Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use JSON.NET to handle a value that is sometimes an object and sometimes an array of the object?

I notice there are some other results on stackoverflow for this question but they don't seem to work or are vague. Using the most popular result, I have put together this:

The problem is that when JSON comes back and is being serialised into one of my custom types, one of the bits of JSON is sometimes an array, and sometimes just a string. If my custom type has a string, and the JSON is an array, I get an error. The same happens the other way around, if the JSON is an object and my custom type is an array. It just can't map it to the property.

I decided to solve this, I want to override the deserialisation of this particular property, and if it's an object, I want to convert it into an array of 1 object.

In the object I am serialising to, I added a JsonConverter which I think is going to override the way it's deserialised.

[JsonConverter(typeof(ArrayOrSingleObjectConverter<string>))]
public List<string> person { get; set; }

The idea is that the custom converter will convert a single object to an array. So if the JSON is "Hello" it will set person to be a List containing "Hello" instead of throwing an exception saying cannot convert string to List.

If it's already an array, it should just leave it alone.

The converter looks like this:

public class ArrayOrSingleObjectConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true; // Not sure about this but technically it can accept an array or an object, so everything is game.
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (objectType == typeof(List<T>))
        {
            return serializer.Deserialize<List<T>>(reader);
        }
        else
        {
            var singleObject = serializer.Deserialize<T>(reader);
            var objectAsList = new List<T>();
            objectAsList.Add(singleObject);
            return objectAsList;
        }
    }

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

It doesn't work. The above code throws an exception trying to deserialize a a single string saying it can't cast it into a List inside the if statement (the 'objectype' is however a List).

I'm struggling to understand exactly what the read and write methods are doing. In the other answer on stackoverflow, it suggests throwing a NotImplementedException in the read method. But if I do that, the read method is called and the exception throws.

I think I'm on the right track, but I need a nudge in the right direction. I think I'm a little confused about what the ReadJSon method is doing and what its parameters mean.

I don't really understand where the value is coming from that it's deserializing since I didn't specify it in the Deserialize method call.

I'm a bit out of my depth on this one.

like image 226
NibblyPig Avatar asked May 04 '13 00:05

NibblyPig


People also ask

How do you handle both a single item and an array for the same property?

The ToObjectCollectionSafe<TResult>() method can handle that for you. This is usable for Single Result vs Array using JSON.net and handle both a single item and an array for the same property and can convert an array to a single object.

How does JSON Deserialization work?

In Deserialization, it does the opposite of Serialization which means it converts JSON string to custom . Net object. In the following code, it calls the static method DeserializeObject() of the JsonConvert class by passing JSON data. It returns a custom object (BlogSites) from JSON data.


2 Answers

I had to do something similar last week and I came up with the following, which works fine for a List rather than an array

 internal class GenericListCreationJsonConverter<T> : JsonConverter
{

    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override bool CanRead
    {
        get { return true; }
    }

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

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartArray)
        {
            return serializer.Deserialize<List<T>>(reader);
        }
        else
        {
            T t = serializer.Deserialize<T>(reader);
            return new List<T>(new[] { t });
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
like image 117
SNorris Avatar answered Sep 20 '22 18:09

SNorris


I like this method which makes Json.NET do all the heavy lifting. And as a result, it supports anything that Json.NET supports (List<>, ArrayList, strongly-typed arrays, etc).

public class FlexibleCollectionConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartArray)
        {
            return serializer.Deserialize(reader, objectType);
        }

        var array = new JArray(JToken.ReadFrom(reader));
        return array.ToObject(objectType);
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof (IEnumerable).IsAssignableFrom(objectType);
    }
}
like image 40
Tim Schmidt Avatar answered Sep 20 '22 18:09

Tim Schmidt