Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Json.NET Custom JsonConverter with data types

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.

like image 225
Jaka Konda Avatar asked May 01 '16 17:05

Jaka Konda


Video Answer


1 Answers

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

like image 119
Brian Rogers Avatar answered Sep 27 '22 17:09

Brian Rogers