Search code examples
c#jsonjson.netjson-deserialization

Json.NET can't Deserialize Indexed Property into Array


This is a stretch, but it's a real world example, yet fluffified.. We have a Data node with multiple Fluff objects in a list. They are named according to their index in the list, as such: { Fluff_0, Fluff_1, ..., Fluff_n }

When I deserialize this object I would want it to be deserialized into a List<Fluff>.

Is there a way to decorate this with JsonPropertyAttributes so that I would get an ordered generic List (or other collection) of Fluff objects in the Data object?

[TestFixture]
public class FluffDeserializationTests
{
    [Test]
    public void FluffDeserialization()
    {
        var json = "{\"Data\": { \"Fluff_0\": {\"ID\": \"abc\"}, \"Fluff_1\": { \"ID\": \"abd\" } } }";
        var result = JsonConvert.DeserializeObject<Fluff>(json);
        Assert.That(result.Data.Things != null);
        Assert.That(result.Data.Things.Count, Is.EqualTo(2));
        Assert.That(result.Data.Things[0].ID, Is.EqualTo("abc"));
        Assert.That(result.Data.Things[1].ID, Is.EqualTo("abd"));
    }


    public class Fluff
    {
        public Data Data { get; set; }
    }

    public class Data
    {
        public List<Thing> Things { get; set; }
    }

    public class Thing
    {
        public string ID { get; set; }
    }
}

Solution

  • There is not an attribute in Json.Net that will directly convert your fluffy object into a list, but you can make a custom JsonConverter to do it without too much trouble, then decorate your Data class with an attribute to tell Json.Net to use the converter.

    Here is what the converter might look like. Note that if there happen to be any gaps in the sequence of Fluff_n keys, this code will still maintain their relative order in the resulting list, but the gaps will not be preserved. I'm guessing this won't be an issue, but just FYI.

    class FluffDataConverter : JsonConverter
    {
        readonly int PrefixLength = "Fluff_".Length;
    
        public override bool CanConvert(Type objectType)
        {
            return (objectType == typeof(Data));
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject jo = JObject.Load(reader);
            List<Thing> things = jo.Properties()
                                   .OrderBy(p => int.Parse(p.Name.Substring(PrefixLength)))
                                   .Select(p => p.Value.ToObject<Thing>())
                                   .ToList();
            return new Data { Things = things };
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    To use this converter, just place a [JsonConverter] attribute on your Data class like this:

    [JsonConverter(typeof(FluffDataConverter))]
    public class Data
    {
        public List<Thing> Things { get; set; }
    }
    

    When you re-rerun your test code, it should work as you described.