theateist
theateist

Reputation: 14421

How to deserialize nested array in my case to custom classes?

Test1 deserializes msg1 that has a single array (data:[]) successfully. ElementJsonConverter handles the ElementData which is more complicated than in the example.

Test2 tries to deserializes msg2 that has a nested array (data:[[]]). Message2 class has Table class which has List<TableRow> when TableRow is List<ElementData> that I need to populate. I don't understand how to do this. Do I need somehow to have separate converters for Table and TableRow?

Single Array

void Test1()
{
   var msg1 = "{\"type\":\"message1\",\"data\":[{\"type\":\"element1\",\"name\":\"myname\",\"amount\":0}]";
   var obj = JsonConvert.DeserializeObject<Message1>(msg1);
}

public class Message1
{
    [JsonProperty("type")]
    public string Type { get; set; }
    [JsonProperty("data", ItemConverterType = typeof(ElementJsonConverter))]
    public List<ElementData> Data { get; set; }
}

public abstract class ElementData
{   
    [JsonProperty("type")]
    public string ElementType { get; set; }

    [JsonProperty("amount")]
    public int Amount { get; set; }
}

public class Element1 : ElementData
{   
    [JsonProperty("name")]
    public string Name{ get; set; }
}

public class Element2 : ElementData
{   
    [JsonProperty("name")]
    public string Name{ get; set; }
    
    //OTHER PROPERTIES
}

Nested Array

void Test2()
{
   var msg2 = "{\"type\":\"message2\",\"data\":[[{\"type\":\"element1\",\"name\":\"myname\",\"amount\":0}]]";
   var obj = JsonConvert.DeserializeObject<Message1>(msg2);
}

public class Message2
{
    [JsonProperty("type")]
    public string Type { get; set; }

    [JsonConverter(typeof(TableJsonConverter))]
    [JsonProperty("data")]
    public Table Data { get; set; }
}

public class Table
{
    public List<TableRow> Steps { get; set; }
}

public class TableRow
{
    [JsonProperty(ItemConverterType = typeof(ElementJsonConverter))]
    public List<ElementData> Elements { get; set; }
}

Converters

public class TableJsonConverter : JsonConverter<Table>
{
    public override Table ReadJson(JsonReader reader, Type objectType, Table existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {          
    }

    public override void WriteJson(...){}
}

public class ElementJsonConverter : JsonConverter<ElementData>
{    
    public override ElementData ReadJson(JsonReader reader, Type objectType, ElementData existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);
        
        var elementType = jObject["type"].Value<string>();
        if(elementType == "element1")
                return jObject.ToObject<Element1>(serializer);
        else if(elementType == "element2")
                return jObject.ToObject<Element2>(serializer);        
        else
            throw new Exception($"Unsupported element type [{elementType}]");
    }

    public override void WriteJson(JsonWriter writer, ElementData value, Newtonsoft.Json.JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

Upvotes: 0

Views: 138

Answers (2)

Guru Stron
Guru Stron

Reputation: 143393

For the provided code (without missing converters) you can just deserialize to collection of collection of elements:

public override Table ReadJson(JsonReader reader, Type objectType, Table existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
    // TODO - handle existing value?
    var deserialize = serializer.Deserialize<Element[][]>(reader); // or ElementData if converter handles it
    return new Table
    {
        Steps = deserialize.Select(x => new TableRow
        {
            Elements = x.ToList<ElementData>()
        })
        .ToList()
    };
    return default;
} 

For more dynamic approach you can use var jArr = JArray.Load(reader) and process it.

UPD

Changed var deserialize = serializer.Deserialize<Element[][]>(reader); to var deserialize = serializer.Deserialize<ElementData[][]>(reader); with following changes to the ElementData class:

[JsonConverter(typeof(ElementJsonConverter))]
public abstract class ElementData
{   
    [JsonProperty("type")]
    public string ElementType { get; set; }

    [JsonProperty("amount")]
    public int Amount { get; set; }
}

And converter:

public class ElementJsonConverter : JsonConverter
{
    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
        JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);

        var elementType = jObject["type"].Value<string>();
        ElementData value;
        if (elementType == "element1")
            value = new Element1();
        else if (elementType == "element2")
            value = new Element2();
        else
            throw new Exception($"Unsupported element type [{elementType}]");

        serializer.Populate(jObject.CreateReader(), value);
        return value;
    }

    public override bool CanConvert(Type objectType) => typeof(ElementData) == objectType;

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

Upvotes: 1

Serge
Serge

Reputation: 43959

IMHO I'd rather to use one root class, if you want more, it is better to create a separate converter for each class

public class Message
{
    public string Type { get; set; }

    public List<List<ElementData>> Data { get; set; }

    [JsonConstructor]
    public Message(string type,JToken data)
    {
        Type = type;
        if(type == "message2")
                Data = data.Select(x => x.Select(y => (ElementData)y.ToObject<Element>()).ToList()).ToList();
            else Data = new List<List<ElementData>> { data.Select(x => (ElementData)x.ToObject<Element>()).ToList() };
    }
}

or if you like converters

public class MessageJsonConverter : JsonConverter<Message>
{
    public override Message ReadJson(JsonReader reader, Type objectType, Message existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var data = (JObject)serializer.Deserialize(reader);

        return new Message
        {
            Type = data["type"].ToString(),
            Data = data["type"].ToString()=="message2"
                   ? data["data"].Select(x => x.Select(y => (ElementData)y.ToObject<Element>()).ToList()).ToList()
                   : new List<List<ElementData>> { data["data"].Select(x => (ElementData)x.ToObject<Element>()).ToList() }
        };
    }

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

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

Upvotes: 0

Related Questions