使用 Json.Net 的多态 JSON 反序列化失败
Polymorphic JSON Deserialization failing using Json.Net
我正在尝试使用自定义 JsonConverter
将一些 JSON 反序列化为各种子 classes
我跟this差不多到重点了
我的抽象基础-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();
}
而我的派生 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
只是一个包含一些字符串常量的静态 class。
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 中的菜单项 "Submenus Test" 缺少 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;
}
您收到错误的原因是因为您的 MenuItem
class 被 标记为 abstract
。我猜你这样做是为了在继承的 classes.
中强制执行 DisplayContents()
方法
根据 的建议,允许读取 Json 的另一种方法是为您的 MenuItem 结构创建一个基础 Interface
,让您的 MenuItem
class 使用 DisplayContents()
方法的基本版本实现接口,将其标记为虚拟,然后在继承的子classes 中覆盖它。
这种方法将确保您在调用 DisplayContents()
时始终会得到显示的内容,并消除您遇到的错误。
classes 和界面的非常粗糙和简化的版本:
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); } }
我正在尝试使用自定义 JsonConverter
我跟this差不多到重点了
我的抽象基础-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();
}
而我的派生 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
只是一个包含一些字符串常量的静态 class。
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 中的菜单项 "Submenus Test" 缺少 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;
}
您收到错误的原因是因为您的 MenuItem
class 被 标记为 abstract
。我猜你这样做是为了在继承的 classes.
DisplayContents()
方法
根据 Interface
,让您的 MenuItem
class 使用 DisplayContents()
方法的基本版本实现接口,将其标记为虚拟,然后在继承的子classes 中覆盖它。
这种方法将确保您在调用 DisplayContents()
时始终会得到显示的内容,并消除您遇到的错误。
classes 和界面的非常粗糙和简化的版本:
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); } }