Подтвердить что ты не робот

В чем разница между PreserveReferencesHandling и ReferenceLoopHandling в Json.Net?

Я рассматриваю один пример приложения WebAPI, который имеет этот код:

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

а другой с этим кодом:

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

Ни один из них не объясняет, почему каждый выбран. Я очень новичок в WebAPI, поэтому кто-то может помочь, объяснив мне простыми словами, каковы различия и почему мне может понадобиться использовать один над другим.

4b9b3361

Ответ 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. Если ваши данные являются ориентированными ациклическими графами без дубликатов ссылок, вам не требуется настройка.