Zoltán Tamási
Zoltán Tamási

Reputation: 12764

Why Json.NET cannot deserialize JSON arrays into object property?

Temporary note: This is NOT a duplicate of the above mentioned post

Let's say I have a server-side class structure like this.

public class Test
{
    // this can be any kind of "Tag"
    public object Data { get; set; }
}

public class Other
{
    public string Test { get; set; }
}

Now a string like this is coming from let's say the client.

{"Data": [{$type: "MyProject.Other, MyProject", "Test": "Test"}] }

When I try to deserialize this into a Test instance, I get a result where the Tag property is a JToken instead of some kind of collection, for example ArrayList or List<object>.

I understand that Json.NET cannot deserialize into a strongly typed list, but I'd expect that it respects that it's at least a list.

Here is my current deserialization code.

var settings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.Auto,
};

var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);

// this first assertion fails
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();

I'm aware of the fact that if I serialize such a structure, then by default I'll get a { $type: ..., $values: [...]} structure in the JSON string instead of a pure array literal, and that will indeed properly deserialize. However, the client is sending a pure array literal, so I should be able to handle that in some way.

Upvotes: 1

Views: 872

Answers (2)

Zolt&#225;n Tam&#225;si
Zolt&#225;n Tam&#225;si

Reputation: 12764

I managed to put together a JsonConverter to handle these kind of untyped lists. The converter applies when the target type is object. Then if the current token type is array start ([) it will force a deserialization into List<object>. In any other case it will fall back to normal deserialization.

This is a first version which passes my most important unit tests, however as I'm not a Json.NET expert, it might break some things unexpectedly. Please if anyone sees anything what I didn't, leave a comment.

public class UntypedListJsonConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;

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

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

        return serializer.Deserialize<List<object>>(reader);
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(object);
    }
}

Usage example:

var settings = new JsonSerializerSettings()
{
    TypeNameHandling = TypeNameHandling.Auto,
    Converters = new[] { new UntypedListJsonConverter() }
};

var str = "{\"Data\": [{\"$type\": \"MyProject.Other, MyProject\", \"Test\": \"Test\"}] }";
var test = JsonConvert.Deserialize<Test>(str, settings);
// now these assertions pass
(test.Data is IList).ShouldBeTrue();
(((IList)test.Data)[0] is Other).ShouldBeTrue();

Upvotes: 1

Corey Berigan
Corey Berigan

Reputation: 634

Try this:

public class Test
{
    public Dictionary<string, List<Other>> Data { get; } = new Dictionary<string, List<Other>>();
}

You need to set up the class you are trying to fill from json data to match as closely to the json structure. From the looks of it, the json looks a dictionary where the keys are strings and the values are arrays of Other objects.

Upvotes: 0

Related Questions