这些设置最好通过示例来解释。假设我们要表示公司中员工的层次结构。所以我们制作了一个这样的简单类:
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 引用bob 而bob 引用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 更加简洁,可以反序列化回原始对象层次结构,无需额外工作,假设您使用的库可以理解 Json.Net 生成的 $id 和 $ref 表示法/ 网络 API。
那么您为什么要选择一种设置呢?当然,这取决于您的需求。如果 JSON 将被不理解 $id/$ref 格式的客户端使用,并且它可以容忍某些地方的数据不完整,那么您将选择使用 ReferenceLoopHandling.Ignore。如果您正在寻找更紧凑的 JSON,并且您将使用 Json.Net 或 Web API(或其他兼容库)来反序列化数据,那么您将选择使用 PreserveReferencesHandling.Objects。如果您的数据是没有重复引用的有向无环图,那么您不需要任何设置。