1

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.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
TarasBulba
  • 668
  • 1
  • 5
  • 11
  • 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](http://stackoverflow.com/q/18994685/10263) The accepted answer has a `SingleOrArrayConverter` class that you should be able to use here. – Brian Rogers Dec 01 '14 at 03:25
  • @BrianRogers You just made my evening! Thank you so much. My only concern is how does it handle sometimes an object situation? – TarasBulba Dec 01 '14 at 04:12
  • 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 Dec 01 '14 at 04:14
  • @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 Dec 01 '14 at 04:26
  • Yes, you should check whether `current_call` is not null, like you said. That, along with the converter, should do the trick. – Brian Rogers Dec 01 '14 at 04:32

1 Answers1

3

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
Community
  • 1
  • 1
Brian Rogers
  • 125,747
  • 31
  • 299
  • 300