使用Json.Net失败的多态JSON反序列化

我正在尝试使用自定义JsonConverter将一些JSON反序列化为各种子类

我几乎遵循了这一点。

我的抽象基类:

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

而我派生的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()) { case SourceType.File: return item.ToObject(); case SourceType.Folder: return item.ToObject(); case SourceType.Json: return item.ToObject(); case SourceType.RestGet: return item.ToObject(); case SourceType.Rss: return item.ToObject(); case SourceType.Text: return item.ToObject(); case SourceType.Url: return item.ToObject(); default: throw new ArgumentException("Invalid source type"); } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } 

SourceType只是一个包含一些字符串常量的静态类。

JSON文件反序列化如下:

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

现在,我的问题是每当我运行代码时,我都会收到以下错误:

 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. 

有问题的Json文件如下所示:

 { "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" } 

在我看来,它无法反序列化嵌套对象,我该如何解决这个问题呢?

首先,json中的菜单项“Submenus Test”错过了SourceType

其次,你不应该简单地使用ToObject因为ToObject属性应该递归处理。

以下ReadJson将起作用:

 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jObject = JObject.Load(reader); var sourceType = jObject["SourceType"].Value(); 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; } 

您收到错误的原因是因为您的MenuItem类被标记为abstract 。 我猜你这样做是为了在inheritance的类中强制执行DisplayContents()方法。

与Mouhong Lin建议的一样,允许读取Json的另一种方法是为MenuItem结构创建一个基本Interface ,让你的MenuItem类用DisplayContents()方法的基本版本实现接口,将其标记为虚拟然后在inheritance的子类中覆盖它。
这种方法将确保您在调用DisplayContents()时始终显示一些内容,并删除您获得的错误。

类和接口的非常粗略和简化版本:

 public interface IMenuItem { String Title { get; set; } String Contents { get; set; } List 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 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); } }