Polymorphic JSON Deserialization failing using Json.Net

c#, json, json.net, serialization

Solution

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;
}

Problem

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?

Original source

Related problems