以下通过具体代码示例和序列化结果,详细说明 PreserveReferencesHandling 各选项的行为差异。
示例背景
我们定义两个类,模拟对象间的循环引用和数组的重复引用:
using Newtonsoft.Json;
using System;
// 模拟对象循环引用:A 引用 B,B 引用 A
public class Person {
public string Name { get; set; }
public Person Friend { get; set; } // 指向另一个 Person 实例(可能形成循环)
}
// 模拟数组重复引用:两个 Team 实例共享同一个 Members 数组
public class Team {
public string TeamName { get; set; }
public Person[] Members { get; set; } // 可能被多个 Team 引用
}
1. PreserveReferencesHandling.None(默认)
行为:不保留引用信息,直接序列化对象内容。若存在循环引用,会抛出异常;若同一对象被多次引用,反序列化后生成独立实例。
示例代码
// 构造循环引用对象:A 的 Friend 是 B,B 的 Friend 是 A
Person personA = new Person { Name = "Alice" };
Person personB = new Person { Name = "Bob", Friend = personA };
personA.Friend = personB; // 形成循环
try {
string json = JsonConvert.SerializeObject(
personA,
Formatting.Indented,
new JsonSerializerSettings {
PreserveReferencesHandling = PreserveReferencesHandling.None
}
);
Console.WriteLine("序列化结果(无循环时):");
Console.WriteLine(json);
}
catch (JsonSerializationException ex) {
Console.WriteLine($"序列化失败(循环引用):{ex.Message}");
}
输出结果
序列化失败(循环引用):检测到循环引用对象图。可能的解决方法是在 JsonSerializerSettings 中设置 ReferenceLoopHandling。路径 'Friend.Friend'。
结论
- 若对象图存在循环引用(如本例中的 personA 和 personB 互相引用),None 模式会直接抛出异常。
- 若对象无循环但被多次引用(例如两个属性指向同一对象),序列化时会生成两个独立的 JSON 对象,反序列化后这两个属性会指向不同的实例(丢失原始引用关系)。
2. PreserveReferencesHandling.Objects
行为:保留普通对象(非数组 / 集合)的引用信息。首次出现的对象标记 ref,反序列化时重建引用关系。
示例代码
// 构造循环引用对象(同上)
Person personA = new Person { Name = "Alice" };
Person personB = new Person { Name = "Bob", Friend = personA };
personA.Friend = personB;
string json = JsonConvert.SerializeObject(
personA,
Formatting.Indented,
new JsonSerializerSettings {
PreserveReferencesHandling = PreserveReferencesHandling.Objects
}
);
Console.WriteLine("序列化结果:");
Console.WriteLine(json);
// 反序列化验证引用关系
Person deserializedA = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine($"\n反序列化后,A 的 Friend 是否是 B? {deserializedA.Friend.Name == "Bob"}");
Console.WriteLine($"B 的 Friend 是否是 A? {deserializedA.Friend.Friend.Name == "Alice"}");
序列化结果
{
"$id": "1",
"Name": "Alice",
"Friend": {
"$id": "2",
"Name": "Bob",
"Friend": {
"$ref": "1"
}
}
}
反序列化输出
反序列化后,A 的 Friend 是否是 B? True
B 的 Friend 是否是 A? True
结论
- id:1,personB 的 $id:2)。
- ref:1,即 personA)。
- 反序列化后,对象间的循环引用关系被完整保留。
3. PreserveReferencesHandling.Arrays
行为:保留数组 / 集合的引用信息。首次出现的数组标记 ref,普通对象的引用不处理。
示例代码
// 构造重复引用的数组:两个 Team 共享同一个 Members 数组
Person[] members = new Person[] {
new Person { Name = "Alice" },
new Person { Name = "Bob" }
};
Team team1 = new Team { TeamName = "Team1", Members = members };
Team team2 = new Team { TeamName = "Team2", Members = members }; // 与 team1 共享 members 数组
string json = JsonConvert.SerializeObject(
new { team1, team2 }, // 序列化包含两个 Team 的匿名对象
Formatting.Indented,
new JsonSerializerSettings {
PreserveReferencesHandling = PreserveReferencesHandling.Arrays
}
);
Console.WriteLine("序列化结果:");
Console.WriteLine(json);
// 反序列化验证数组引用关系
var result = JsonConvert.DeserializeObject<dynamic>(json);
bool isSameArray = result.team1.Members == result.team2.Members; // 检查是否指向同一数组
Console.WriteLine($"\n反序列化后,两个 Team 的 Members 是否是同一数组? {isSameArray}");
序列化结果
{
"$id": "1",
"team1": {
"$id": "2",
"TeamName": "Team1",
"Members": {
"$id": "3",
"$values": [
{
"$id": "4",
"Name": "Alice",
"Friend": null
},
{
"$id": "5",
"Name": "Bob",
"Friend": null
}
]
}
},
"team2": {
"$id": "6",
"TeamName": "Team2",
"Members": {
"$ref": "3"
}
}
}
反序列化输出
反序列化后,两个 Team 的 Members 是否是同一数组? True
结论
- 数组 members 被标记为 ref:3 引用该数组。
- 普通对象(如 Person 实例)的引用未被处理(id:5 是独立对象)。
4. PreserveReferencesHandling.All
行为:同时保留对象和数组的引用信息(等价于 Objects | Arrays)。所有首次出现的对象(包括数组)标记 ref。
示例代码
// 构造同时包含对象循环和数组重复引用的场景
Person personA = new Person { Name = "Alice" };
Person personB = new Person { Name = "Bob", Friend = personA };
personA.Friend = personB; // 对象循环
Person[] members = new Person[] { personA, personB }; // 数组包含循环对象
Team team1 = new Team { TeamName = "Team1", Members = members };
Team team2 = new Team { TeamName = "Team2", Members = members }; // 数组重复引用
string json = JsonConvert.SerializeObject(
new { team1, team2 },
Formatting.Indented,
new JsonSerializerSettings {
PreserveReferencesHandling = PreserveReferencesHandling.All
}
);
Console.WriteLine("序列化结果:");
Console.WriteLine(json);
// 反序列化验证所有引用关系
var result = JsonConvert.DeserializeObject<dynamic>(json);
bool isSameArray = result.team1.Members == result.team2.Members;
bool isFriendLoop = result.team1.Members[0].Friend.Name == "Bob"
&& result.team1.Members[1].Friend.Name == "Alice";
Console.WriteLine($"\n数组引用是否一致? {isSameArray}");
Console.WriteLine($"对象循环是否保留? {isFriendLoop}");
序列化结果(关键部分)
{
"$id": "1",
"team1": {
"$id": "2",
"TeamName": "Team1",
"Members": {
"$id": "3",
"$values": [
{
"$id": "4",
"Name": "Alice",
"Friend": {
"$id": "5",
"Name": "Bob",
"Friend": {
"$ref": "4"
}
}
},
{
"$ref": "5"
}
]
}
},
"team2": {
"$id": "6",
"TeamName": "Team2",
"Members": {
"$ref": "3"
}
}
}
反序列化输出
数组引用是否一致? True
对象循环是否保留? True
结论
- 数组(id:4,personB 的 $id:5)的引用均被保留。
- 反序列化后,数组的重复引用和对象的循环引用均被完整重建。
总结表格
选项
处理对象类型
循环引用处理
重复引用处理
适用场景
None
无
抛出异常
生成独立实例(丢失引用)
简单对象图(无循环 / 重复)
Objects
普通对象
保留循环关系
保留对象引用(数组不处理)
对象间循环 / 重复引用
Arrays
数组 / 集合
不处理对象循环(仍抛异常)
保留数组引用(对象不处理)
数组 / 集合的重复引用
All
对象 + 数组 / 集合
保留循环关系
保留所有引用(对象 + 数组)
复杂对象图(混合循环 + 重复)
参考:AI豆包