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

StackOverflow https://stackoverflow.com/questions/16369450

Question

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.

Was it helpful?

Solution

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();
    }

OTHER TIPS

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);
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top