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

59 c# asp.net json json.net asp.net-web-api

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

json.SerializerSettings.PreserveReferencesHandling 
   = Newtonsoft.Json.PreserveReferencesHandling.Objects;
Run Code Online (Sandbox Code Playgroud)

和另一个有这个编码:

json.SerializerSettings.ReferenceLoopHandling 
   = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
Run Code Online (Sandbox Code Playgroud)

既没有解释为什么选择每一个.我是WebAPI的新手,所以有人可以通过简单的方式向我解释这些差异是什么以及为什么我可能需要使用一个而不是另一个.

Bri*_*ers 136

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

class Employee
{
    public string Name { get; set; }
    public List<Employee> Subordinates { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是一家迄今为止只有三名员工的小公司: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 };
Run Code Online (Sandbox Code Playgroud)

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

string json = JsonConvert.SerializeObject(employees, Formatting.Indented);
Console.WriteLine(json);
Run Code Online (Sandbox Code Playgroud)

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

[
  {
    "Name": "Angela Anderson",
    "Subordinates": [
      {
        "Name": "Bob Brown",
        "Subordinates": null
      },
      {
        "Name": "Charles Cooper",
        "Subordinates": null
      }
    ]
  },
  {
    "Name": "Bob Brown",
    "Subordinates": null
  },
  {
    "Name": "Charles Cooper",
    "Subordinates": null
  }
]
Run Code Online (Sandbox Code Playgroud)

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

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

class Employee
{
    public string Name { get; set; }
    public Employee Supervisor { get; set; }
    public List<Employee> Subordinates { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

...并在我们的设置代码中添加几行,以表明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 };
Run Code Online (Sandbox Code Playgroud)

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

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

string json = JsonConvert.SerializeObject(employees, settings);
Run Code Online (Sandbox Code Playgroud)

有了这个设置,我们得到以下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
  }
]
Run Code Online (Sandbox Code Playgroud)

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

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

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

string json = JsonConvert.SerializeObject(employees, settings);
Run Code Online (Sandbox Code Playgroud)

现在我们得到以下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"
  }
]
Run Code Online (Sandbox Code Playgroud)

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

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

  • 很好的答案.当使用最后一个方法(`PreserveReferencesHandling.Objects`)时,[JsonNetDecycle](https://bitbucket.org/smithkl42/jsonnetdecycle)是一个非常棒的库,用于重新组装对象引用客户端. (9认同)