1
votes

I have a Json object as follows:

{
"response" : {
  "method" : "switchvox.currentCalls.getList",
  "result" : {
     "current_calls" : {
        "current_call" : **[**
           {
              "provider" : "ThinkTel",
              "start_time" : "2014-11-30 02:24:52",
              "duration" : "5",
              "to_caller_id_number" : "800",
              "state" : "ivr",
              "from_caller_id_name" : "<unknown>",
              "to_caller_id_name" : "Main Answer Queue",
              "format" : "ulaw",
              "from_caller_id_number" : "9999999999",
              "id" : "SIP/1234567890-08682a00"
           },
           {
              "provider" : "ThinkTel",
              "start_time" : "2014-11-30 02:24:50",
              "duration" : "7",
              "to_caller_id_number" : "800",
              "state" : "ivr",
              "from_caller_id_name" : "<unknown>",
              "to_caller_id_name" : "Main Answer Queue",
              "format" : "ulaw",
              "from_caller_id_number" : "1111111111",
              "id" : "SIP/9876543210-08681350"
           }
        **]**,
        "total_items" : "2"
     }
  }
 }
}

My classes were built using http://json2csharp.com

Everything is good until my

New Data allCalls = JsonConvert.DeserializeObject<Data>(json);

gets only 1 or 0 call in the array (if there is more than 1, all works). The [] in Json objects are removed when there is only 1 call and 0 calls, the whole current_call block is not there.

Here is how it looks without calls:

 {
 "response" : {
  "method" : "switchvox.currentCalls.getList",
  "result" : {
     "current_calls" : {
        "total_items" : "0"
      }
    }
  }
}

And here how it looks with only 1 call:

{
"response" : {
  "method" : "switchvox.currentCalls.getList",
  "result" : {
     "current_calls" : {
        "current_call" : {
           "provider" : "Internal",
           "start_time" : "2014-11-30 19:15:44",
           "duration" : "250",
           "to_caller_id_number" : "777",
           "state" : "talking",
           "from_caller_id_name" : "<unknown>",
           "to_caller_id_name" : "<unknown>",
           "format" : "ulaw->ulaw",
           "from_caller_id_number" : "231",
           "id" : "SIP/231-b4066e90"
        },
        "total_items" : "1"
   }
  }
 }
}

I get the following error:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'WindowsService1.Service1+CurrentCall[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.

To fix this error I either need to change the JSON to a JSON array (e.g. [1,2,3]) or deserialized type to become normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) 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.

I understand what the error means but I don't now how to approach it in my situation.

1
Since current_call is sometimes an object and sometimes an array, you will need to use a JsonConverter to handle this. See How to handle both a single item and an array for the same property using JSON.net The accepted answer has a SingleOrArrayConverter<T> class that you should be able to use here.Brian Rogers
@BrianRogers You just made my evening! Thank you so much. My only concern is how does it handle sometimes an object situation?TarasBulba
It handles both, that is the point. You declare your class to have a list property, then if the JSON has a single object, you will get a list with one item instead of an error. If the JSON has an array, then you still get a list (with multiple items).Brian Rogers
@BrianRogers I meant what if no object at all like in my example? I believe i still have to implement this part? Or I can just check if (parser.allCalls.response.result.current_calls.current_call != null)TarasBulba
Yes, you should check whether current_call is not null, like you said. That, along with the converter, should do the trick.Brian Rogers

1 Answers

3
votes

Since current_call is sometimes an object, sometimes an array and sometimes not there at all, you will need to use a JsonConverter in order to avoid errors. You can use the SingleOrArrayConverter<T> converter from this similar question and combine it with a null check to get a workable solution. Here is a demo that covers all three scenarios:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("--- array of calls ---");
        DeserializeAndDumpCallsList(@"
        {
            ""response"": {
                ""method"": ""switchvox.currentCalls.getList"",
                ""result"": {
                    ""current_calls"": {
                        ""current_call"": [
                            {
                                ""provider"": ""ThinkTel"",
                                ""start_time"": ""2014-11-30 02:24:52"",
                                ""duration"": ""5"",
                                ""to_caller_id_number"": ""800"",
                                ""state"": ""ivr"",
                                ""from_caller_id_name"": ""<unknown>"",
                                ""to_caller_id_name"": ""Main Answer Queue"",
                                ""format"": ""ulaw"",
                                ""from_caller_id_number"": ""9999999999"",
                                ""id"": ""SIP/1234567890-08682a00""
                            },
                            {
                                ""provider"": ""ThinkTel"",
                                ""start_time"": ""2014-11-30 02:24:50"",
                                ""duration"": ""7"",
                                ""to_caller_id_number"": ""800"",
                                ""state"": ""ivr"",
                                ""from_caller_id_name"": ""<unknown>"",
                                ""to_caller_id_name"": ""Main Answer Queue"",
                                ""format"": ""ulaw"",
                                ""from_caller_id_number"": ""1111111111"",
                                ""id"": ""SIP/9876543210-08681350""
                            }
                        ],
                        ""total_items"": ""2""
                    }
                }
            }
        }");

        Console.WriteLine("--- single call ---");
        DeserializeAndDumpCallsList(@"
        {
            ""response"": {
                ""method"": ""switchvox.currentCalls.getList"",
                ""result"": {
                    ""current_calls"": {
                        ""current_call"": {
                            ""provider"": ""Internal"",
                            ""start_time"": ""2014-11-30 19:15:44"",
                            ""duration"": ""250"",
                            ""to_caller_id_number"": ""777"",
                            ""state"": ""talking"",
                            ""from_caller_id_name"": ""<unknown>"",
                            ""to_caller_id_name"": ""<unknown>"",
                            ""format"": ""ulaw->ulaw"",
                            ""from_caller_id_number"": ""231"",
                            ""id"": ""SIP/231-b4066e90""
                        },
                        ""total_items"": ""1""
                    }
                }
            }
        }");

        Console.WriteLine("--- no current call ---");
        DeserializeAndDumpCallsList(@"
        {
            ""response"": {
                ""method"": ""switchvox.currentCalls.getList"",
                ""result"": {
                    ""current_calls"": {
                        ""total_items"": ""0""
                    }
                }
            }
        }");
    }

    private static void DeserializeAndDumpCallsList(string json)
    {
        var root = JsonConvert.DeserializeObject<RootObject>(json);
        List<CurrentCall> calls = root.response.result.current_calls.current_call;

        if (calls != null)
        {
            foreach (CurrentCall call in calls)
            {
                Console.WriteLine(call.from_caller_id_number);
            }
        }
        else
        {
            Console.WriteLine("no calls");
        }

        Console.WriteLine();
    }

    public class RootObject
    {
        public Response response { get; set; }
    }

    public class Response
    {
        public string method { get; set; }
        public Result result { get; set; }
    }

    public class Result
    {
        public CurrentCalls current_calls { get; set; }
    }

    public class CurrentCalls
    {
        [JsonConverter(typeof(SingleOrArrayConverter<CurrentCall>))]
        public List<CurrentCall> current_call { get; set; }
        public string total_items { get; set; }
    }

    public class CurrentCall
    {
        public string provider { get; set; }
        public string start_time { get; set; }
        public string duration { get; set; }
        public string to_caller_id_number { get; set; }
        public string state { get; set; }
        public string from_caller_id_name { get; set; }
        public string to_caller_id_name { get; set; }
        public string format { get; set; }
        public string from_caller_id_number { get; set; }
        public string id { get; set; }
    }

    class SingleOrArrayConverter<T> : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(List<T>));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken token = JToken.Load(reader);
            if (token.Type == JTokenType.Array)
            {
                return token.ToObject<List<T>>();
            }
            return new List<T> { token.ToObject<T>() };
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
}

Output:

--- array of calls ---
9999999999
1111111111

--- single call ---
231

--- no current call ---
no calls