1

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.

Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93
  • Did you use `JsonSerializerSettings` and specify typehandling? In any case, if you want to ask "why doesn't my code work" you need to post your code, so post the deserialization code. – Lasse V. Karlsen Nov 23 '16 at 08:29
  • How should it know that `object` should be a `List` in this case? Should it try to match the received JSON against all fitting types in the assembly? That would be very slow and would not yield any stable result. – Matthias247 Nov 23 '16 at 08:43
  • related http://stackoverflow.com/questions/21470697/how-to-deserialize-an-untyped-object-using-json-net-or-datacontractjsonserialize – Ian Ringrose Nov 23 '16 at 08:43
  • Possible duplicate of [Deserialize Dictionary with enum values in C#](http://stackoverflow.com/questions/38336390/deserialize-dictionarystring-object-with-enum-values-in-c-sharp) – Ian Ringrose Nov 23 '16 at 08:45
  • Edited, removed the `Dictionary` part entirely because my problem is reproducible without that too. Hope now it will get less criticism and more constructive thoughts. – Zoltán Tamási Nov 23 '16 at 08:56
  • Could anyone please remove the duplicate label, and also the one who downvoted, could you please remove it, or at least add some reasons? – Zoltán Tamási Nov 23 '16 at 16:05
  • I never deserialized JSON, but why do you expect something that is deserialized to be mutable and an `IList<>`? I would have expected it to be `IEnumerable<>` (not sure if it is) and nothing more. – nvoigt Nov 24 '16 at 11:31
  • @nvoigt That's worth another discussion I think. In the scope of this question it doesn't matter, I'd just expect *a collection*. By the way as JSON is "coming" from JavaScript, I associate `[ ... ]` literal in the JSON as an *array*. And arrays in JavaScript are just very similar to `IList` interface, that's why I mentioned `IList`. But as I said, it doesn't matter, I'd be happy with `IEnumerable`. – Zoltán Tamási Nov 24 '16 at 11:36

2 Answers2

1

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();
Zoltán Tamási
  • 12,249
  • 8
  • 65
  • 93
0

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.

Corey Berigan
  • 614
  • 4
  • 14
  • Thank you, of course if I could I would do it this way, but using an `object` value is intentional. The dictionary is kind of a custom property holder, hence it could contains almost anything. – Zoltán Tamási Nov 23 '16 at 07:36