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

时间:2014-05-04 08:04:54

标签: c# asp.net json asp.net-web-api json.net

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

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

和另一个用这个编码:

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

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

2 个答案:

答案 0 :(得分:131)

最好通过示例解释这些设置。让我们说我们想要代表公司员工的等级。所以我们创建一个这样的简单类:

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就会更加简洁,并且可以反序列化回原始对象层次结构,无需额外的工作,假设您使用的库知道生成的$id$ref符号通过Json.Net / Web API。

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

答案 1 :(得分:0)

解释很完美。对我来说,下面的一个工作,数据是你的对象。
但是,如果上述方法不起作用,您可以试试这个:

string json = JsonConvert.SerializeObject(data, Formatting.Indented,new JsonSerializerSettings
{
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});