【问题标题】:Polymorphic JSON Deserialization failing using Json.Net使用 Json.Net 的多态 JSON 反序列化失败
【发布时间】:2015-05-21 08:37:30
【问题描述】:

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

我关注this 几乎到了重点。

我的抽象基类:

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 只是一个包含一些字符串常量的静态类。

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

在我看来,反序列化嵌套对象有问题,我该如何解决?

【问题讨论】:

  • 我发现如果我使用 JsonConvert.DeserializeObject 并通过添加了我的转换器的序列化程序设置,我得到了完全相同的错误。当我使用与您相同的重载时...我不知道这是错误的 JsonSerializer 还是我对序列化程序设置的理解。

标签: c# json serialization json.net


【解决方案1】:

首先,您的 json 中的菜单项“子菜单测试”缺少 SourceType

其次,您不应该因为 Submenus 属性而简单地使用 ToObject,它应该被递归处理。

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

【讨论】:

  • 在我让它工作后我会做你的“顺便说一句”,原始代码没有多态性,但我决定扩展它以模块化功能
  • 我删除了那个“btw”。我刚刚发现您目前在基地MenuItem 中的房产应该是必不可少的。 :P
  • 对于其他有类似问题的人,这也适用于抽象类的任何多态反序列化。我在没有自引用结构的情况下获得了 StackOverflowException,因为 jObject.ToObject(targetType) 会递归地将我送回。
【解决方案2】:

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

Mouhong Lin 建议的不同,允许读取Json 的方法是为您的MenuItem 结构创建一个基本Interface,让您的MenuItem 类使用@ 的基本版本实现接口987654327@ 方法,将其标记为虚拟,然后在继承的子类中覆盖它。
这种方法将确保您在调用 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); } }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多