Solving Json discrepancies using a JsonConverter | Derik Whittaker

:

During my day job I am a client developer who is beholden to various back end API’s. Normally these API’s are well thought out and pretty easy to use, and normally their resulting json model make sense. But as we know, normally does not mean always. A few weeks ago I was implementing a feature which and built a object model based on the response from the API and the needs of my feature. Fast forward to present time and I am implementing another feature, similar but different. At first glace of the resulting json coming from the API it dawned on me that I could resume my object model from before (this makes sense since the features are in the same scope and are very tightly coupled).

As I set out to code my solution I hit a point where I needed to desearlize my json document to my object graph. No big deal as I am using Json.net and this is cake. But we know that nothing in software is cake. I immediately get an error while deserializing my graph, but why.

Below is my original json graph

"participants": [
{
  "id": "1ab644ce-481d-443f-a569-202a06cad740",      
  "calls": {
      "id": "c72537f6-af1e-4afe-abc2-5815b25960eb"
    }
},       

This graph looks pretty normal (note that most properties were removed as they were just noise). I have an array of participants and each participant has a calls subobject.

No look at the json graph from the 2nd api hit

"participants": [
{
  "id": "1ab644ce-481d-443f-a569-202a06cad740",      
  "calls": [
    {
      "id": "c72537f6-af1e-4afe-abc2-5815b25960eb"
    }
  ]
},

Do you see the subtle difference? In the second graph the Calls subobject is not an object but rather an array of objects. WTF????

I stared at this problem for some time trying to find the most elegant way to solve the problem and be able to resume my object graph, since the graph is expected to be the same.

The solution I found was to use a JsonConverter which will allow me to custom convert my Calls node as needed.

The first thing I needed to do is decorate my Call property in my object graph as below

        [JsonProperty("calls")]
        [JsonConverter(typeof(VoiceConversationCallsConverter))]
        public VoiceConversationCall Call { get; set; }

The next thing I need to do is create my VoiceConversationCallsConverter. Inside this converter I am going to determine if the node currently being parsed is an object or an array and return back the correct value.

    public class VoiceConversationCallsConverter : JsonConverter 
    {
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.StartObject)
            {
                return serializer.Deserialize(reader);
            }
            
            if (reader.TokenType == JsonToken.StartArray)
            {
                var asArray = serializer.Deserialize<List>(reader);
                if (asArray != null && asArray.Any())
                {
                    return asArray.First();
                }

                return new VoiceConversationCall();
            }

            return serializer.Deserialize(reader);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }

        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    }

Pay attention to the ReadJson method. Inside this method I am looking at the TokenType for the current node and asking if it is an object or an array. once I know the TokenType I can deserialize my reader into the correct type and return it as needed.

Doing this allowed me to have a single object graph (again, which is expected) and process two slightly different json graphs.

Till next time,