Polymorphic JSON Deserialization does not work with Json.Net

I am trying to deserialize some JSON for different subclasses using 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 resulting 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 containing some string constants.

The JSON file is deserialized as follows:

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

Now my problem 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 corresponding Json file is as follows:

 { "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 seems to me that he has problems with deserializing nested objects, how can I get around this?

+8
json c # serialization
Mar 18 '15 at 14:01
source share
2 answers

Firstly, SourceType skipped for the menu item "Test submenu" in your json.

Secondly, you should not just use ToObject because of the Submenus property, which must be processed 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; } 
+14
Mar 18 '15 at 14:39
source share

The reason you get the error is because your MenuItem class is marked abstract . I assume you did this to provide an implementation of the DisplayContents() method in legacy classes.

Another way to let Json be read, to which Mouhong Lin suggested making an Interface base for your MenuItem structure, having MenuItem implements the interface with the base version of the DisplayContents() method, marks it virtual, and then overrides it in your inherited subclasses.
This approach ensures that you always get something that is displayed when DisplayContents() called, and remove the error you received.

A very crude and simplified version of the classes and interface:

 public interface IMenuItem { String Title { get; set; } String Contents { get; set; } List<MenuItem> Submenus { get; set; } String Source { get; set; } String SourceType { get; set; } void DisplayContents(); } public class MenuItem: IMenuItem { 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 virtual void DisplayContents() { MessageBox.Show(Title); } } // Very very basic implementation of the classes, just to show what can be done public class FileMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title + this.GetType().ToString()); } } public class FolderMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title + "Folder Class"); } } public class JsonMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Contents); } } public class RestMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Source); } } public class RssMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(SourceType); } } public class TextMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title); } } public class UrlMenu : MenuItem { public override void DisplayContents() { MessageBox.Show(Title); } } 
0
Mar 18 '15 at 2:48
source share



All Articles