8

Currently I'm facing the following issue when I try to deserialize a json string that I receive from a 3rd party (--> I cannot change the received json string by myself) with Newtonsoft.Json:

The json contains a dictionary (and some other entries that I don't list here):

"food": {
        "Menu 1": "abc",
        "Menu 2": "def"
}

I've created a class that contains the property (plus the properties that I didn't list here):

Dictionary<string, string> Food {get; set;}

In this case the desiralization of the json works fine. The problem occurs when food is empty:

{
      "food": [],
}

In this case it seems that food is not a dictionary but an array. That's the reason why the desirialization fails with the following error:

Newtonsoft.Json.JsonSerializationException: "Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,System.String]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array. Path 'food'."

Is anybody out there who can help me to solve this problem, please?

EDIT The desiralization code:

public T DeserializeAPIResults<T>(string json)
{
        JObject obj = JsonConvert.DeserializeObject<JObject>(json);
        return obj.GetValue("canteen").ToObject<T>();
}

EDIT 2 Full json with values:

        {
        "canteen": [
            {
                "name": "Canteen1",
                "src": "a link",
                "food": {
                    "Menu 1": "abc",
                    "Menu 2": "def",
                    "Menu 3": "ghi",
                    "Menu 4": "jkl",
                    "Menu 5": "mno"
                }
            },
            {
                "name": "Canteen2",
                "src": "a link",
                "food": {
                    "Menu 1": "abc",
                    "Menu 2": "def",
                    "Menu 3": "ghi"
                }
            },
            {
                "name": "Canteen3",
                "src": "a link",
                "food": {
                    "Line 1": "abc",
                    "Line 2": "def",
                    "Line 3": "ghi"
                }
            }
        ]
    }

Full json without values:

{
    "canteen": [
        {
            "name": "Canteen1",
            "src": "a link",
            "food": [],
        },
        {
            "name": "Canteen2",
            "src": "a link",
            "food": [],
        },
        {
            "name": "Canteen3",
            "src": "a link",
            "food": [],
       }
    ]
}

EDIT 3 The class:

public sealed class Canteen
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("src")]
    public string Src { get; set; }
    [JsonProperty("food")]
    public Dictionary<string, string> Food { get; set; }
    public Canteen() { }
}

And the method call:

Canteen[] canteens = DeserializeAPIResults<Canteen[]>(json);
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
CLRW97
  • 470
  • 4
  • 15

1 Answers1

5

If your value for particular key are not fixed and data must be configurable then Newtonsoft.json has one feature that to be use here and that is [JsonExtensionData]. Read more

Extension data is now written when an object is serialized. Reading and writing extension data makes it possible to automatically round-trip all JSON without adding every property to the .NET type you’re deserializing to. Only declare the properties you’re interested in and let extension data do the rest.

When your 3rd party json have an key with name food and its value as object then you are trying to deserialze into Dictionary<string, string> Food {get; set;} and your deserilization method correctly deserialize your json.

But when food key have array then your method fails to deserialize because you are trying to deserialize array [] into string.

If you use

[JsonExtensionData]
public Dictionary<string, JToken> Food { get; set; }

Instead of

Dictionary<string, string> Food {get; set;}

Then your deserialization works.

So finally your class will be

public sealed class Canteen
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("src")]
    public string Src { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JToken> Food { get; set; }
    public Canteen() { }
}

Alternative:

If you declare your Food property data type to JToken in your Canteen class like

public sealed class Canteen
{
    [JsonProperty("name")]
    public string Name { get; set; }
    [JsonProperty("src")]
    public string Src { get; set; }

    [JsonProperty("food")]
    public JToken Food { get; set; }

    public Canteen() { }
}

Then you can successfully deserialize your json whether your food key is either object or array.

And then you can access your each of canteen from canteens array and retrieve each canteen's name, src and food key/value pair like.

The advantage of JToken is that you can check it's type whether its object or array

Canteen[] canteens = DeserializeAPIResults<Canteen[]>(json);

foreach (var canteen in canteens)
{
    string name = canteen.Name;
    string src = canteen.Src;
    JToken food = canteen.Food;

    if (food.Type == JTokenType.Object)
    {
        Dictionary<string, string> foods = food.ToObject<Dictionary<string, string>>();
    }
    else if (food.Type == JTokenType.Array)
    {
        //Do something if "foods" is empty array "[]"
    }
}
er-sho
  • 9,581
  • 2
  • 13
  • 26
  • Thank you very much! I just tried your solution and it worked well for me. I have not seen the feature [JsonExtensionData] before. The only thing I ask myself is why the 3rd party does that. Why can't they just return a blank dictionary instead of an array if there's no data? – CLRW97 Nov 24 '18 at 14:14
  • Glad to hear, and i also have same question about that. Anyways welcomes yours :) – er-sho Nov 24 '18 at 14:22
  • I've also added alternative to `[JsonExtensionData]`. kindly view **Alternative** section in answer :) – er-sho Nov 24 '18 at 15:31
  • Thank you for your second solution to this problem. I think I will use this because it seems a little more useful to my approach. Anyway, the first version is good too :) – CLRW97 Nov 24 '18 at 20:42