Ответ 1
Эти настройки лучше всего объяснить на примере. Позвольте сказать, что мы хотим представлять иерархию сотрудников в компании. Поэтому мы делаем простой класс следующим образом:
class Employee
{
public string Name { get; set; }
public List<Employee> Subordinates { get; set; }
}
Это небольшая компания с тремя сотрудниками: Анджела, Боб и Чарльз. Анджела - босс, а Боб и Чарльз - ее подчиненные. Позвольте настроить данные для описания этого отношения:
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, потому что объекты, представляющие их, ссылаются как на главный список сотрудников, так и на список подчиненных Анжелы. Возможно, сейчас ОК.
Теперь предположим, что мы также хотели бы иметь возможность следить за каждым руководителем Employee в дополнение к своим подчиненным. Поэтому мы меняем нашу модель Employee
, чтобы добавить свойство Supervisor
...
class Employee
{
public string Name { get; set; }
public Employee Supervisor { get; set; }
public List<Employee> Subordinates { get; set; }
}
... и добавьте еще пару строк в наш установочный код, чтобы показать, что Чарльз и Боб отчитываются Анджеле:
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 ни Боб, ни Чарльз не показывают супервизора. В нижней части JSON Боб и Чарльз показывают Анджелу в качестве своего руководителя, но заметили, что ее список подчиненных в этот момент не включает в себя как Боба, так и Чарльза.
Пока можно работать с этим JSON и, возможно, даже восстановить оригинальную иерархию объектов из него с некоторой работой, это явно не оптимально. Мы можем исключить повторную информацию в JSON, сохраняя при этом ссылки на объекты, используя вместо этого параметр PreserveReferencesHandling
:
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/Web API.
Так почему бы вам выбрать один параметр или другой? Конечно, это зависит от ваших потребностей. Если JSON будет потребляться клиентом, который не понимает формат $id
/$ref
, и он может переносить неполные данные в местах, вы должны использовать ReferenceLoopHandling.Ignore
. Если вы ищете более компактный JSON, и вы будете использовать Json.Net или Web API (или другую совместимую библиотеку) для десериализации данных, вы можете использовать PreserveReferencesHandling.Objects
. Если ваши данные являются ориентированными ациклическими графами без дубликатов ссылок, вам не требуется настройка.