小编典典

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

c#

我正在查看一个具有以下代码的WebAPI应用程序示例:

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

另一个与此编码:

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

都没有解释为什么选择每个。我是WebAPI的新手,所以有人可以通过简单地向我解释差异是什么以及为什么可能需要在另一个之上使用我来提供帮助。


阅读 602

收藏
2020-05-19

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

到目前为止,一切都很好。但是,您会注意到,在JSON中重复了Bob和Charles的信息,因为代表员工的对象同时被雇员的主要列表和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引用bob时为bobreference
angela),所以JsonSerializationException当我们尝试序列化雇员列表时,我们将得到一个。解决此问题的一种方法是设置ReferenceLoopHandlingIgnore

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

请注意,现在已为每个对象$id在JSON中分配了一个顺序值。第一次出现某个对象时,它会被完全序列化,而随后的引用将被一个特殊$ref属性替换,该属性会使用相应的属性返回原始对象$id。有了这个设置,JSON是更简洁,可以不需要额外的工作进行反序列化回原来的对象的层次结构,假设你使用的是理解图书馆$id$ref通过Json.Net
/网络API产生的符号。

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

2020-05-19