首页 > 解决方案 > 反序列化关系模型的 Newtonsoft.Json 问题

问题描述

我有一个具有父子关系的简单关系模型,如下所示:

    public class Parent
    {
        public Parent(int id)
        {
            Id = id;
        }

        public int Id { get; private set; }
        public IList<Child> Children { get; set; } = new List<Child>();
    }

    public class Child
    {
        public Parent Parent { get; set; }
    }

我创建了一个小对象图,其中包含共享同一父级的两个子级的列表:

    var parent = new Parent(1);
    var child1 = new Child {Parent = parent};
    var child2 = new Child {Parent = parent};
    parent.Children.Add(child1);
    parent.Children.Add(child2);

    var data = new List<Child> {child1, child2};

接下来,我使用以下方法对其进行序列化SerializeObject

    var settings = new JsonSerializerSettings
    {
        PreserveReferencesHandling = PreserveReferencesHandling.All
    };
    var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);

据我所见,生成的 json 看起来不错:

{
  "$id": "1",
  "$values": [
    {
      "$id": "2",
      "Parent": {
        "$id": "3",
        "Id": 1,
        "Children": {
          "$id": "4",
          "$values": [
            {
              "$ref": "2"
            },
            {
              "$id": "5",
              "Parent": {
                "$ref": "3"
              }
            }
          ]
        }
      }
    },
    {
      "$ref": "5"
    }
  ]
}

但是,当我反序列化 json 时,我没有得到预期的对象,因为对于第二个孩子,Parent 属性为空,导致第二个断言失败:

    var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);
    Debug.Assert(data2[0].Parent != null);
    Debug.Assert(data2[1].Parent != null);

如果没有构造函数,Parent则不会出现此问题并且第二个孩子的 Parent 属性具有预期值。

任何想法这可能是什么?

标签: c#jsonasp.net-corejson.net

解决方案


参数化构造函数不能很好地工作,PreserveReferencesHandling因为存在固有的先有鸡还是先有蛋的问题:在反序列化程序需要创建对象时,可能无法从 JSON 加载传递给构造函数的必要信息。所以需要有一种方法让它创建一个空对象,然后稍后填写适当的信息。

这是一个已知的限制,并记录在案

注意:
当通过非默认构造函数设置值时,无法保留引用。对于非默认构造函数,必须在父值之前创建子值,以便将它们传递给构造函数,从而无法跟踪引用。ISerializabletypes 是一个类的示例,其值使用非默认构造函数填充,并且不适用于PreserveReferencesHandling.

要解决您的模型的问题,您可以在您的Parent类中创建一个私有的、无参数的构造函数并将其标记为[JsonConstructor]. 然后用 标记Id属性[JsonProperty],这将允许 Json.Net 使用私有设置器。所以你会有:

public class Parent
{
    public Parent(int id)
    {
        Id = id;
    }

    [JsonConstructor]
    private Parent()
    { }

    [JsonProperty]
    public int Id { get; private set; }
    public IList<Child> Children { get; set; } = new List<Child>();
}

顺便说一句,由于您的所有列表都没有在对象之间共享,因此您可以PreserveReferencesHandling.Objects使用PreserveReferencesHandling.All. 这将使 JSON 更小,但仍会保留您关心的引用。

在这里工作演示:https ://dotnetfiddle.net/cLk9DM


推荐阅读