I stumbled upon a service that outputs JSON in the following format:
{
"Author": "me",
"Version": "1.0.0",
"data.Type1": {
"Children": [
{
"data.Type1": {
"Children": [
{
"data.Type2": {
"name": "John",
"surname": "Doe"
}
}
]
}
},
{
"data.Type3": {
"dob": "1990-01-01"
}
}
]
}
}
Data type names are preserved as property names and their values are the actual objects. They all start with a data.
prefix.
What I'd like to get afterwards is something like this:
{ // Root
"Author": "me",
"Version": "1.0.0",
"Children": [ // Type1
{
"Children": [ // Type1
{ // Type2
"Name": "John",
"Surname": "Doe"
}
]
},
{ // Type3
"DoB": "1990-01-01"
}
]
}
with the following classes:
class Type1 {
ICollection<object> Children { get; set; }
}
class Type2 {
public string Name { get; set; }
public string Surname { get; set; }
}
class Type3 {
public DateTime DoB { get; set; }
}
class Root
{
public string Author { get; set; }
public string Version { get; set; }
public Type1 Children { get; set; }
}
Question
How can I deserialize this into added C# classes, taking into account the data types and removing them from the tree?
I've tried with a custom JsonConverter
, but am struggling with how to dynamically choose the converter, since the easiest way would be to put an attribute on the property, but it is not supported.
A small example would be great.
Although this JSON format is somewhat unusual and resists the use of attributes due to the dynamic property names, it is still possible to make a JsonConverter
to deserialize it into your preferred class structure with one small change: I would recommend you change the Children
property in the Root
class to be an ICollection<object>
to mirror the Children
property in the Type1
class. As it is now, it does not match the structure of your desired output (where Children
is shown as an array, not an object) and would otherwise require additional code in the converter to handle properly.
class Root
{
public string Author { get; set; }
public string Version { get; set; }
public ICollection<object> Children { get; set; }
}
Here is what I came up with for the converter (assuming the above change is made):
class CustomConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Root));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = JObject.Load(reader);
Root root = new Root();
root.Author = (string)obj["Author"];
root.Version = (string)obj["Version"];
root.Children = ((Type1)DeserializeTypeX(obj, serializer)).Children;
return root;
}
private object DeserializeTypeX(JObject obj, JsonSerializer serializer)
{
JProperty prop = obj.Properties().Where(p => p.Name.StartsWith("data.")).First();
JObject child = (JObject)prop.Value;
if (prop.Name == "data.Type1")
{
List<object> children = new List<object>();
foreach (JObject jo in child["Children"].Children<JObject>())
{
children.Add(DeserializeTypeX(jo, serializer));
}
return new Type1 { Children = children };
}
else if (prop.Name == "data.Type2")
{
return child.ToObject<Type2>(serializer);
}
else if (prop.Name == "data.Type3")
{
return child.ToObject<Type3>(serializer);
}
throw new JsonSerializationException("Unrecognized type: " + prop.Name);
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Armed with this converter you can deserialize to your classes like this:
Root root = JsonConvert.DeserializeObject<Root>(json, new CustomConverter());
You can then serialize to the new format like this:
JsonSerializerSettings settings = new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd",
Formatting = Formatting.Indented
};
Console.WriteLine(JsonConvert.SerializeObject(root, settings));
Fiddle: https://dotnetfiddle.net/ESNMLE
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With