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

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

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

另一个与这个编码:

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

也不解释为什么select每个。 我对WebAPI很陌生,有人可以通过简单地向我解释两者之间的区别,以及为什么我可能需要使用它们。

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

 class Employee { public string Name { get; set; } public List<Employee> Subordinates { get; set; } } 

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

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

到现在为止还挺好。 但是,您会注意到,在JSON中重复使用Bob和Charles的信息是因为表示它们的对象既是由主要员工列表所引用的,也是由Angela的下属列表引用的。 也许现在好了。

现在假设我们也希望有一种方式来跟踪每个员工的主pipe和他或她的下属。 因此,我们更改我们的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都不显示主pipe。 在JSON的底部,鲍勃和查尔斯都把安吉拉当作他们的主pipe,但是注意到她的下属名单并不包括鲍勃和查尔斯。

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

 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值。 第一次出现一个对象时,它会被完全序列化,而后面的引用被replace为一个特殊的$ref属性,该属性用相应的$id引用原始对象。 有了这个设置,JSON就更简洁了,可以反序列化回原始的对象层次结构,不需要额外的工作,假设你使用了一个理解由Json.Net / Web产生的$id$ref表示法的库API。

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