3

One of the JSON API that I am consuming returns response that varies its data structure depending upon how many results are returned from the query. I am consuming it from C# and using JSON.NET to deserialize the response.

Here is the JSON that is returned from the API

Multiple Result Response:

{
  "response": {
    "result": {
      "Leads": {
        "row": [
          {
            "no": "1",
...
...
...

Single Result Response:

{
  "response": {
    "result": {
      "Leads": {
        "row": {
          "no": "1",
...
...
...

Note the difference at "row" node which is either is an array in case of multiple results and object in case of single result.

Here are classes that I use to deserialize this data

Classes:

public class ZohoLeadResponseRootJson
{
    public ZohoLeadResponseJson Response { get; set; }
}

public class ZohoLeadResponseJson
{
    public ZohoLeadResultJson Result { get; set; }
}

public class ZohoLeadResultJson
{
    public ZohoDataMultiRowJson Leads { get; set; }
}

public class ZohoDataMultiRowJson
{
    public List<ZohoDataRowJson> Row { get; set; }
}

public class ZohoDataRowJson
{
    public int No { get; set; }
    ...
}

The "Multiple Result Response" is deserialized without any problem but when there is only one result in the response, because of the data structure change, the response can't be deserialized. I get an exception

Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON 
object (e.g. {"name":"value"}) into type 
'System.Collections.Generic.List`1[MyNamespace.ZohoDataRowJson]' 
because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.

To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) 
or change the deserialized type so that it is a normal .NET type (e.g. not a 
primitive type like integer, not a collection type like an array or List<T>) 
that can be deserialized from a JSON object. JsonObjectAttribute can also be 
added to the type to force it to deserialize from a JSON object.

Path 'response.result.Notes.row.no', line 1, position 44.

Is there a way to handle this in Json.Net with some attribute and hopefully without having to write a converter?

amit_g
  • 30,880
  • 8
  • 61
  • 118
  • Thanks @L.B for the link and the answer in the linked question. – amit_g Sep 08 '14 at 19:49
  • Another similar question http://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n – amit_g Sep 08 '14 at 19:52
  • @L.B. in ReadJson method reader.ValueType is always null. In this case I had to use reader.TokenType which is StartArray or StartObject. Also, in both cases the deserialization would continue. I would like to post an answer as it is little different than both linked answers. I would also like to name the vendor so that this can help future users running into the same issue. – amit_g Sep 08 '14 at 20:15
  • I reopened the question... – L.B Sep 08 '14 at 20:17

1 Answers1

4

The is inspired by an answer to a similar question.

public class ZohoDataMultiRowJson
{
    [JsonConverter(typeof(ArrayOrObjectConverter<ZohoDataRowJson>))]
    public List<ZohoDataRowJson> Row { get; set; }
}

public class ArrayOrObjectConverter<T> : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartArray)
        {
            return serializer.Deserialize<List<T>>(reader);
        }
        else if (reader.TokenType == JsonToken.StartObject)
        {
            return new List<T>
            {
                (T) serializer.Deserialize<T>(reader)
            };
        }
        else
        {
            throw new NotSupportedException("Unexpected JSON to deserialize");
        }
    }

    public override bool CanConvert(Type objectType)
    {
        throw new NotImplementedException();
    }
}
Community
  • 1
  • 1
amit_g
  • 30,880
  • 8
  • 61
  • 118
  • How about making the answer more generic, not only for *ZohoDataRowJson*? That way it would be perfect. BTW a +1 – L.B Sep 08 '14 at 20:28