Reputation: 14421
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
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
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