Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有啥区别?

Posted

技术标签:

【中文标题】Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有啥区别?【英文标题】:What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有什么区别? 【发布时间】:2014-06-20 15:54:51 【问题描述】:

我正在查看一个具有此编码的 WebAPI 应用程序示例:

json.SerializerSettings.PreserveReferencesHandling 
   = Newtonsoft.Json.PreserveReferencesHandling.Objects;

还有一个用这个编码的:

json.SerializerSettings.ReferenceLoopHandling 
   = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

都没有解释为什么选择每个。我对 WebAPI 很陌生,所以有人可以通过简单的方式向我解释它们之间的区别以及为什么我可能需要使用其中一个来帮助我。

【问题讨论】:

文档是怎么说的?有什么不清楚的地方? 【参考方案1】:

这些设置最好通过示例来解释。假设我们要表示公司中员工的层次结构。所以我们制作了一个这样的简单类:

class Employee

    public string Name  get; set; 
    public List<Employee> Subordinates  get; set; 

这是一家小公司,到目前为止只有三名员工:Angela、Bob 和 Charles。安吉拉是老板,鲍勃和查尔斯是她的下属。让我们设置数据来描述这种关系:

Employee angela = new Employee  Name = "Angela Anderson" ;
Employee bob = new Employee  Name = "Bob Brown" ;
Employee charles = new Employee  Name = "Charles Cooper" ;

angela.Subordinates = new List<Employee>  bob, charles ;

List<Employee> employees = new List<Employee>  angela, bob, charles ;

如果我们将员工列表序列化为 JSON...

string json = JsonConvert.SerializeObject(employees, Formatting.Indented);
Console.WriteLine(json);

...我们得到这个输出:

[
  
    "Name": "Angela Anderson",
    "Subordinates": [
      
        "Name": "Bob Brown",
        "Subordinates": null
      ,
      
        "Name": "Charles Cooper",
        "Subordinates": null
      
    ]
  ,
  
    "Name": "Bob Brown",
    "Subordinates": null
  ,
  
    "Name": "Charles Cooper",
    "Subordinates": null
  
]

到目前为止一切顺利。但是,您会注意到,Bob 和 Charles 的信息在 JSON 中重复出现,因为代表他们的对象同时被员工的主列表和 Angela 的下属列表引用。也许现在还可以。

现在假设我们还希望有一种方法来跟踪每个员工的主管以及他或她的下属。所以我们改变我们的Employee 模型来添加一个Supervisor 属性...

class Employee

    public string Name  get; set; 
    public Employee Supervisor  get; set; 
    public List<Employee> Subordinates  get; set; 

...并在我们的设置代码中添加几行以表明 Charles 和 Bob 向 Angela 报告:

Employee angela = new Employee  Name = "Angela Anderson" ;
Employee bob = new Employee  Name = "Bob Brown" ;
Employee charles = new Employee  Name = "Charles Cooper" ;

angela.Subordinates = new List<Employee>  bob, charles ;
bob.Supervisor = angela;       // added this line
charles.Supervisor = angela;   // added this line

List<Employee> employees = new List<Employee>  angela, bob, charles ;

但是现在我们遇到了一点问题。因为对象图中有引用循环(例如angela 引用bobbob 引用angela),当我们尝试序列化员工列表时,我们将得到一个JsonSerializationException。我们可以解决此问题的一种方法是将ReferenceLoopHandling 设置为Ignore,如下所示:

JsonSerializerSettings settings = new JsonSerializerSettings

    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    Formatting = Formatting.Indented
;

string json = JsonConvert.SerializeObject(employees, settings);

有了这个设置,我们得到以下 JSON:

[
  
    "Name": "Angela Anderson",
    "Supervisor": null,
    "Subordinates": [
      
        "Name": "Bob Brown",
        "Subordinates": null
      ,
      
        "Name": "Charles Cooper",
        "Subordinates": null
      
    ]
  ,
  
    "Name": "Bob Brown",
    "Supervisor": 
      "Name": "Angela Anderson",
      "Supervisor": null,
      "Subordinates": [
        
          "Name": "Charles Cooper",
          "Subordinates": null
        
      ]
    ,
    "Subordinates": null
  ,
  
    "Name": "Charles Cooper",
    "Supervisor": 
      "Name": "Angela Anderson",
      "Supervisor": null,
      "Subordinates": [
        
          "Name": "Bob Brown",
          "Subordinates": null
        
      ]
    ,
    "Subordinates": null
  
]

如果您检查 JSON,应该清楚此设置的作用:任何时候序列化程序遇到对已在序列化过程中的对象的引用,它只是跳过该成员。 (这可以防止序列化程序陷入无限循环。)您可以看到,在 JSON 顶部的 Angela 的下属列表中,Bob 和 Charles 都没有显示主管。在 JSON 的底部,Bob 和 Charles 都将 Angela 显示为他们的主管,但请注意此时她的下属列表不包括 Bob 和 Charles。

虽然可以使用这个 JSON,甚至可以通过一些工作从它重建原始对象层次结构,但这显然不是最优的。我们可以通过使用 PreserveReferencesHandling 设置来消除 JSON 中的重复信息,同时仍然保留对象引用:

JsonSerializerSettings settings = new JsonSerializerSettings

    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented
;

string json = JsonConvert.SerializeObject(employees, settings);

现在我们得到以下 JSON:

[
  
    "$id": "1",
    "Name": "Angela Anderson",
    "Supervisor": null,
    "Subordinates": [
      
        "$id": "2",
        "Name": "Bob Brown",
        "Supervisor": 
          "$ref": "1"
        ,
        "Subordinates": null
      ,
      
        "$id": "3",
        "Name": "Charles Cooper",
        "Supervisor": 
          "$ref": "1"
        ,
        "Subordinates": null
      
    ]
  ,
  
    "$ref": "2"
  ,
  
    "$ref": "3"
  
]

请注意,现在每个对象都在 JSON 中分配了一个连续的 $id 值。对象第一次出现时,它会被完全序列化,而随后的引用被替换为特殊的$ref 属性,该属性用相应的$id 引用原始对象。有了这个设置,JSON 更加简洁,可以反序列化回原始对象层次结构,无需额外工作,假设您使用的库可以理解 Json.Net 生成的 $id$ref 表示法/ 网络 API。

那么您为什么要选择一种设置呢?当然,这取决于您的需求。如果 JSON 将被不理解 $id/$ref 格式的客户端使用,并且它可以容忍某些地方的数据不完整,那么您将选择使用 ReferenceLoopHandling.Ignore。如果您正在寻找更紧凑的 JSON,并且您将使用 Json.Net 或 Web API(或其他兼容库)来反序列化数据,那么您将选择使用 PreserveReferencesHandling.Objects。如果您的数据是没有重复引用的有向无环图,那么您不需要任何设置。

【讨论】:

优秀的答案。当使用最后一种方法时(PreserveReferencesHandling.Objects)JsonNetDecycle 是一个很棒的库,用于在客户端重新组装对象引用。 谢谢,实际上我只是需要它,因为PreserveReferencesHandling.Objects 的结果在将其用于列表时不是很好,不确定 JsonNetDecycle 是否会在浏览器中产生大量开销【参考方案2】:

解释很到位。对我来说,以下一项有效,数据是您的对象。 但是,如果上述方法不起作用,您可以尝试以下方法:

string json = JsonConvert.SerializeObject(data, Formatting.Indented,new JsonSerializerSettings

    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
);

【讨论】:

以上是关于Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

忽略 Json.net 中的空字段

确保 .NET 中的 json 键是小写的

.NET 中的 LINQ to JSON

Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有啥区别?

使用 json.net 中的属性序列化 DataSet/DataTable

Json.NET 中的 RawJSON 对象