反序列化关系模型的 Newtonsoft.Json 问题

Posted

技术标签:

【中文标题】反序列化关系模型的 Newtonsoft.Json 问题【英文标题】:Newtonsoft.Json issue with deserialising relational model 【发布时间】:2020-07-27 00:59:41 【问题描述】:

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

    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属性有预期值。

有什么想法吗?

【问题讨论】:

是不是因为你只在序列化上设置 PreserveReferencesHandling 而不是在反序列化上,所以它不知道如何读取 $refs? 请分享生成的准确 JSON。另外,你读过newtonsoft.com/json/help/html/PreserveObjectReferences.htm 吗? 我更新了代码以在反序列化时也使用 PreserveReferencesHandling。但是,这没有任何效果我还包括了生成的 JSON 【参考方案1】:

参数化构造函数不能很好地与PreserveReferencesHandling 一起工作,因为存在一个先天先有鸡的问题:在反序列化程序需要创建目的。所以需要有一种方法让它创建一个空对象,然后再填写适当的信息。

这是一个已知的限制,是documented:

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

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

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

【讨论】:

感谢您的详细解释。我自己也在考虑这些属性,但是我对使用特定序列化程序的提示“污染”我的数据模型感到不高兴。您对此有何看法? 哈,好吧,我猜我对这些事情比较务实。如果添加几个属性是我完成工作的唯一障碍,并且不会以任何方式干扰我的设计,那么我将采用这些属性。然而,这只是我。您最终必须决定什么对您和您的代码最有利,因为您将是维护它的人。我可以理解为什么您可能不想在模型库中依赖 Json.Net。在这种情况下,我建议您根本不要序列化您的模型——而是将它们映射到 DTO 并对其进行序列化。【参考方案2】:

我还没有测试过这个,但我觉得如果你把设置放入序列化器和反序列化器中它会解决它:

var settings = new JsonSerializerSettings

    PreserveReferencesHandling = PreserveReferencesHandling.All
;
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);
var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);

【讨论】:

我更新了帖子中的代码片段,以便在反序列化时也使用 PreserveReferencesHandling 设置。但是,does没有任何作用/。 嗯。很奇怪 - 正如你所说的 json 看起来不错【参考方案3】:

第二次尝试:

我重现了这个问题,然后把Parent的构造函数改成无参,Id属性改成可设置,然后就成功了。不知道为什么,但我想这是 Newtonsoft 选择如何构建事物的方式中的一些错误,特别是因为顺序对于 PreserveReferencesHandling 很重要。

public class Parent

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

var parent = new Parent  Id = 1 ;

【讨论】:

是的,我也想通了。但是,我确实更喜欢我的只读模型,其中属性仅在构建时才获得价值。 如果您希望您的模型是纯的,那么您是否考虑过不让它们相互引用?你可以在Parent 上有一个getter,它接收孩子并返回一个包装Child 并引用其Parent 的不同类,这将涵盖所有用例以及以正常方式可序列化。跨度> 就实际问题而言,我认为这可能是 Newtonsoft 中的一个错误,因此如果还没有问题,可能值得打开一个问题。 github.com/JamesNK/Newtonsoft.Json/issues 已提交github.com/JamesNK/Newtonsoft.Json/issues/2315 我不确定我是否完全理解您提出的使模型纯净的建议。你能再解释一下吗?

以上是关于反序列化关系模型的 Newtonsoft.Json 问题的主要内容,如果未能解决你的问题,请参考以下文章

反序列化时的 Newtonsoft.JSON 空值

Newtonsoft.Json 处理多态类型的反序列化

newtonsoft.json 反序列化

如何通过NewtonSoft反序列化对象json列表?

NewtonSoft.JSON 反序列化 - 未正确反序列化。 (VB.NET)

使用Newtonsoft.Json.dll序列化和反序列化