我正在尝试使用自定义JsonConver
将一些JSON反序列化为各种子类
我几乎紧紧抓住了重点。
我的抽象基类:
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();
}
我的派生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();
}
}
<code>SourceType</code>只是一个包含一些字符串常量的静态类。
JSON文件反序列化如下:
JsonConvert.DeserializeObject<MenuItem>(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中的菜单项“子菜单测试”缺少< code>SourceType。
其次,您不应该简单地使用 ToObject
,因为 Submenus
属性应该以递归方式处理。
以下ReadJson
将起作用:
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;
}
出现该错误的原因是因为您的< code>MenuItem类被标记为< code>abstract。我猜您这样做是为了在继承的类中强制实现< code>DisplayContents()方法。
另一种允许Json被读取的方法,林某宏建议,是为您的MenuItem结构制作一个基本的Interface
,让您的MenuItem
类使用DisplayContents()
方法的基本版本实现该接口,将其标记为虚拟,然后在您继承的子类中覆盖它。
这种方法将确保您在调用DisplayContents()
时总是会得到一些显示的内容,并删除您收到的错误。
类和接口的一个非常粗略和简化的版本:
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); } }