Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic JSON Deserialization failing using Json.Net

I'm trying to deserialize some JSON to various sub-classes using a custom JsonConverter

I followed this almost to the point.

My abstract base-class:

abstract class MenuItem
{
    public String Title { get; set; }
    public String Contents { get; set; }
    public List<MenuItem> Submenus { get; set; }
    public String Source { get; set; }
    public String SourceType { get; set; }
    public abstract void DisplayContents();
}

And my derived JsonConverter:

class MenuItemConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(MenuItem).IsAssignableFrom(objectType);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JObject item = JObject.Load(reader);
            switch (item["SourceType"].Value<String>())
            {
                case SourceType.File:    return item.ToObject<Menu.FileMenu>();
                case SourceType.Folder:  return item.ToObject<Menu.FolderMenu>();
                case SourceType.Json:    return item.ToObject<Menu.JsonMenu>();
                case SourceType.RestGet: return item.ToObject<Menu.RestMenu>();
                case SourceType.Rss:     return item.ToObject<Menu.RssMenu>();
                case SourceType.Text:    return item.ToObject<Menu.TextMenu>();
                case SourceType.Url:     return item.ToObject<Menu.UrlMenu>();
                default: throw new ArgumentException("Invalid source type");
            }
        }

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

SourceType is just a static class holding some string constants.

The JSON file is deserialized like this:

JsonConvert.DeserializeObject<MenuItem>(File.ReadAllText(menuPath), new MenuItemConverter());

Now, my issue is that whenever I run the code I get the following error:

An exception of type 'Newtonsoft.Json.JsonSerializationException' occurred in Newtonsoft.Json.dll but was not handled in user code

Additional information: Could not create an instance of type ConsoleMenu.Model.MenuItem. Type is an interface or abstract class and cannot be instantiated. Path 'Submenus[0].Title', line 5, position 21.

The Json file in question looks like this:

{
    "Title": "Main Menu",
    "Submenus": [
        {
            "Title": "Submenu 1",
            "Contents": "This is an example of the first sub-menu",
            "SourceType": "Text"
        },
        {
            "Title": "Submenu 2",
            "Contents": "This is the second sub-menu",
            "SourceType": "Text"
        },
        {
            "Title": "GitHub System Status",
            "Contents": "{\"status\":\"ERROR\",\"body\":\"If you see this, the data failed to load\"}",
            "Source": "https://status.github.com/api/last-message.json",
            "SourceType": "RestGet"
        },
        {
            "Title": "TF2 Blog RSS",
            "Contents": "If you see this message, an error has occurred",
            "Source": "http://www.teamfortress.com/rss.xml",
            "SourceType": "Rss"
        },
        {
            "Title": "Submenus Test",
            "Contents": "Testing the submenu functionality",
            "Submenus": [
                {
                    "Title": "Submenu 1",
                    "Contents": "This is an example of the first sub-menu",
                    "SourceType": "Text"
                },
                {
                    "Title": "Submenu 2",
                    "Contents": "This is the second sub-menu",
                    "SourceType": "Text"
                }
            ]
        }
    ],
    "SourceType": "Text"
}

It appears to me that it has trouble deserializing the nested objects, how do I get around that?

like image 627
Electric Coffee Avatar asked Mar 18 '15 14:03

Electric Coffee


People also ask

Is polymorphic deserialization possible in System text JSON?

There is no polymorphic deserialization (equivalent to Newtonsoft. Json's TypeNameHandling ) support built-in to System. Text. Json .

What is polymorphic JSON?

You can use this schema when defining XML Type hierarchies by using only the base XML Types. The XML schema defines XML Types that inherit from each other. In the JSON, an object carries no additional information about the type.

What is polymorphic deserialization?

A polymorphic deserialization allows a JSON payload to be deserialized into one of the known gadget classes that are documented in SubTypeValidator. java in jackson-databind in GitHub. The deserialized object is assigned to a generic base class in your object model, such as java. lang. Object or java.

How do I deserialize a JSON file?

A common way to deserialize JSON is to first create a class with properties and fields that represent one or more of the JSON properties. Then, to deserialize from a string or a file, call the JsonSerializer. Deserialize method.


1 Answers

Firstly, SourceType is missed for menu item "Submenus Test" in your json.

Secondly, you shouldn't simply use ToObject because of the Submenus property, which should be handled recursively.

The following ReadJson will work:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    var jObject = JObject.Load(reader);
    var sourceType = jObject["SourceType"].Value<string>();

    object target = null;

    switch (sourceType)
    {
        case SourceType.File: 
            target = new FileMenu(); break;
        case SourceType.Folder: 
            target = new FolderMenu(); break;
        case SourceType.Json: 
            target = new JsonMenu(); break;
        case SourceType.RestGet: 
            target = new RestMenu(); break;
        case SourceType.Rss: 
            target = new RssMenu(); break;
        case SourceType.Text: 
            target = new TextMenu(); break;
        case SourceType.Url: 
            target = new UrlMenu(); break;
        default: 
            throw new ArgumentException("Invalid source type");
    }

    serializer.Populate(jObject.CreateReader(), target);

    return target;
}
like image 148
Mouhong Lin Avatar answered Oct 03 '22 12:10

Mouhong Lin