Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有什么区别? [英] What is the difference between PreserveReferencesHandling and ReferenceLoopHandling in Json.Net?

查看:24
本文介绍了Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

和另一个具有此编码的:

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

都没有解释为什么选择每一个.我对 WebAPI 非常陌生,所以有人可以通过简单的术语向我解释有什么区别以及为什么我可能需要使用一个而不是另一个来帮助我.

解决方案

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

class Employee{公共字符串名称 { 获取;放;}公共列表<员工>下属{得到;放;}}

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

Employee angela = new Employee { Name = "Angela Anderson" };员工鲍勃 = 新员工 { 姓名 = "鲍勃布朗" };员工查尔斯 = 新员工{姓名 =查尔斯库珀"};angela.Subordinates = new List{鲍勃,查尔斯};列表<员工>员工 = 新列表<员工>{安吉拉,鲍勃,查尔斯};

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

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

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

<预><代码>[{名称":安吉拉·安德森",下属":[{"姓名": "鲍勃·布朗",下属":空},{名称":查尔斯·库珀",下属":空}]},{"姓名": "鲍勃·布朗",下属":空},{名称":查尔斯·库珀",下属":空}]

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

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

class Employee{公共字符串名称 { 获取;放;}公共职工监事{得到;放;}公共列表<员工>下属{得到;放;}}

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

Employee angela = new Employee { Name = "Angela Anderson" };员工鲍勃 = 新员工 { 姓名 = "鲍勃布朗" };员工查尔斯 = 新员工{姓名 =查尔斯库珀"};angela.Subordinates = new List{鲍勃,查尔斯};bob.Supervisor = 安吉拉;//添加这一行charles.Supervisor = 安吉拉;//添加这一行列表<员工>员工 = 新列表<员工>{安吉拉,鲍勃,查尔斯};

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

JsonSerializerSettings settings = new JsonSerializerSettings{ReferenceLoopHandling = ReferenceLoopHandling.Ignore,格式 = Formatting.Indented};string json = JsonConvert.SerializeObject(employees, settings);

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

<预><代码>[{名称":安吉拉·安德森",主管":空,下属":[{"姓名": "鲍勃·布朗",下属":空},{名称":查尔斯·库珀",下属":空}]},{"姓名": "鲍勃·布朗",导师": {名称":安吉拉·安德森",主管":空,下属":[{名称":查尔斯·库珀",下属":空}]},下属":空},{名称":查尔斯·库珀",导师": {名称":安吉拉·安德森",主管":空,下属":[{"姓名": "鲍勃·布朗",下属":空}]},下属":空}]

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

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

JsonSerializerSettings settings = new JsonSerializerSettings{PreserveReferencesHandling = PreserveReferencesHandling.Objects,格式 = Formatting.Indented};string json = JsonConvert.SerializeObject(employees, settings);

现在我们得到以下 JSON:

<预><代码>[{"$id": "1",名称":安吉拉·安德森",主管":空,下属":[{"$id": "2","姓名": "鲍勃·布朗",导师": {"$ref": "1"},下属":空},{"$id": "3",名称":查尔斯·库珀",导师": {"$ref": "1"},下属":空}]},{"$ref": "2"},{"$ref": "3"}]

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

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

I am looking at one WebAPI application sample that has this coded:

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

and another with this coded:

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

Neither explain why each is chosen. I'm very new to WebAPI, so can someone help by explaining to me in simple terms what the differences are and why I might need to use one over the other.

解决方案

These settings can best be explained by example. Let's say that we want to represent a hierarchy of employees in a company. So we make a simple class like this:

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

This is a small company with only three employees so far: Angela, Bob and Charles. Angela is the boss, while Bob and Charles are her subordinates. Let's set up the data to describe this relationship:

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

If we serialize the list of employees to JSON...

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

...we get this output:

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

So far so good. You'll notice, however, that the information for Bob and Charles is repeated in the JSON because the objects representing them are referenced both by the main list of employees and Angela's list of subordinates. Maybe that's OK for now.

Now suppose we'd also like to have a way to keep track of each Employee's supervisor in addition to his or her subordinates. So we change our Employee model to add a Supervisor property...

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

...and add a couple more lines to our setup code to indicate that Charles and Bob report to 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 };

But now we have a bit of a problem. Because the object graph has reference loops in it (e.g. angela references bob while bob references angela), we will get a JsonSerializationException when we try to serialize the employees list. One way we can get around this issue is by setting ReferenceLoopHandling to Ignore like this:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    Formatting = Formatting.Indented
};

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

With this setting in place, we get the following 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
  }
]

If you examine the JSON, it should be clear what this setting does: any time the serializer encounters a reference back to an object it is already in the process of serializing, it simply skips that member. (This prevents the serializer from getting into an infinite loop.) You can see that in Angela's list of subordinates in the top part of the JSON, neither Bob nor Charles show a supervisor. In the bottom part of the JSON, Bob and Charles both show Angela as their supervisor, but notice her subordinates list at that point does not include both Bob and Charles.

While it is possible to work with this JSON and maybe even reconstruct the original object hierarchy from it with some work, it is clearly not optimal. We can eliminate the repeated information in the JSON while still preserving the object references by using the PreserveReferencesHandling setting instead:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    Formatting = Formatting.Indented
};

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

Now we get the following 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"
  }
]

Notice that now each object has been assigned a sequential $id value in the JSON. The first time that an object appears, it is serialized in full, while subsequent references are replaced with a special $ref property that refers back to the original object with the corresponding $id. With this setting in place, the JSON is much more concise and can be deserialized back into the original object hierarchy with no additional work required, assuming you are using a library that understands the $id and $ref notation produced by Json.Net / Web API.

So why would you choose one setting or the other? It depends on your needs of course. If the JSON will be consumed by a client that does not understand the $id/$ref format, and it can tolerate having incomplete data in places, you would choose to use ReferenceLoopHandling.Ignore. If you're looking for more compact JSON and you will be using Json.Net or Web API (or another compatible library) to deserialize the data, then you would choose to use PreserveReferencesHandling.Objects. If your data is a directed acyclic graph with no duplicate references then you don't need either setting.

这篇关于Json.Net 中的 PreserveReferencesHandling 和 ReferenceLoopHandling 有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆