Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deserialize object that can be an array or a dictionary with Newtonsoft?

I am using an API that returns a json object that I need to deserialize. My problem is that one of the members of those object is sometimes an empty array ("[]") and sometimes a dictionary ("{"1":{...}, "2":{...}}"). I want to deserialize it into either an array or a dictionary, since I don't car about the IDs, I just want a list of all the objects. Here is how I deserialize the object:

var response = JsonConvert.DeserializeObject<Response>(json);

And here is the definition of the Response class:

public class Response
{
    [JsonProperty(PropertyName = "variations")]
    public Dictionary<int, Variation> Variations { get; set; }
}

It works well when the Response contains a dictionary in it's variations field, but it fails when it contains an empty array. I'm getting an error from Newtonsoft saying that an array cannot be deserialized into a dictionary. If I define the Variations property as an array, it works for empty arrays, but it fails when it is a dictionary. What could I do to either deserialize correctly both possible values, or to ignore empty arrays and set Variations to null when it's an array instead of failing.

Thanks.

like image 805
Carl Avatar asked Aug 31 '12 20:08

Carl


People also ask

How do you deserialize an object in C#?

NET objects (deserialize) A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.

Is Newtonsoft JSON deserialize case sensitive?

Case insensitive deserialization: By default, Newtonsoft. Json does case insensitive property name matching during deserialization whereas System. Text.

What does Jsonconvert DeserializeObject do?

DeserializeObject Method. Deserializes the JSON to a . NET object.

What is Jsonconvert SerializeObject C#?

SerializeObject Method (Object, Type, JsonSerializerSettings) Serializes the specified object to a JSON string using a type, formatting and JsonSerializerSettings.


3 Answers

Here an alternative solution using JsonConverter

public class Response
{
    [JsonProperty(PropertyName = "variations")]
    [JsonConverter(typeof(EmptyArrayOrDictionaryConverter))]
    public Dictionary<int, Variation> Variations { get; set; }
}


public class EmptyArrayOrDictionaryConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType.IsAssignableFrom(typeof(Dictionary<string, object>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Object)
        {
            return token.ToObject(objectType, serializer);
        }
        else if (token.Type == JTokenType.Array)
        {
            if (!token.HasValues)
            {
                // create empty dictionary
                return Activator.CreateInstance(objectType);
            }
        }

        throw new JsonSerializationException("Object or empty array expected");
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}
like image 182
Johan van der Slikke Avatar answered Oct 22 '22 11:10

Johan van der Slikke


Here is a variation (sorry for the pun) on Carl's example. I had a similar need, but instead of returning a dictionary, I needed an array. The API I am using says it returns an array. However, in the case where there is only one item in the result, it is returned as an object instead!

public class VarationsContainer
{
    [JsonIgnore]
    public Varation[] Varations
    {
        get
        {
            return ParseObjectToArray<Variation>(VariationObject);
        }
    }

    [JsonProperty(PropertyName = "varations")]
    public object VarationsObject { get; set; }

    protected T[] ParseObjectToArray<T>(object ambiguousObject)
    {
        var json = ambiguousObject.ToString();
        if (String.IsNullOrWhiteSpace(json))
        {
            return new T[0]; // Could return null here instead.
        }
        else if (json.TrimStart().StartsWith("["))
        {
            return JsonConvert.DeserializeObject<T[]>(json);
        }
        else
        {
            return new T[1] { JsonConvert.DeserializeObject<T>(json) };
        }
    }
}

I hope this is useful to some other sad API consumer.

like image 36
Todd Avatar answered Oct 22 '22 11:10

Todd


Here is the solution I used :

    public Dictionary<int, Variation> Variations
    {
        get
        {
            var json = this.VariationsJson.ToString();
            if (json.RemoveWhiteSpace() == EmptyJsonArray)
            {
                return new Dictionary<int, Variation>();
            }
            else
            {
                return JsonConvert.DeserializeObject<Dictionary<int, Variation>>(json);
            }
        }
    }

    [JsonProperty(PropertyName = "variations")]
    public object VariationsJson { get; set; }

Basically, the variations are first deserialized in a basic object. When I want to read the value, I check if the object is an empty array, and if so I return an empty dictionary. If the object is a good dictionary, I deserialize it and return it.

like image 1
Carl Avatar answered Oct 22 '22 12:10

Carl