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

Как вы "действительно" Сериализовать круговые ссылки объектов с помощью Newtonsoft.Json?

У меня возникла проблема с получением некоторых данных, упорядоченных правильно, с моего контроллера веб-API ASP.NET с использованием Newtonsoft.Json.

Вот что, я думаю, происходит - пожалуйста, поправьте меня, если я ошибаюсь. При определенных обстоятельствах (особенно когда в данных нет каких-либо циклических ссылок) все работает так, как вы ожидали, - список заполненных объектов становится сериализованным и возвращенным. Если я введу данные, которые приводят к циклической ссылке в модели (описанной ниже и даже с установкой PreserveReferencesHandling.Objects), только элементы списка, ведущие к первому объекту с циклической ссылкой, получают сериализацию таким образом, что клиент может "работать с". "Элементы, ведущие к", могут быть любыми элементами в данных, если они заказываются по-разному, прежде чем отправлять вещи в сериализатор, но по крайней мере один будет сериализован таким образом, чтобы клиент мог "работать с". Пустые объекты в конечном итоге сериализуются как ссылки Newtonsoft ({$ref:X}).

Например, если у меня есть модель EF, полная свойств навигации, которая выглядит так:

Model

В моем global.asax:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;

Здесь основной запрос, который я использую с помощью Entity Framework (lazy-load выключен, поэтому здесь нет прокси-классов):

[HttpGet]
[Route("starting")]
public IEnumerable<Balance> GetStartingBalances()
{
   using (MyContext db = new MyContext())
   {
       var data = db.Balances
        .Include(x => x.Source)
        .Include(x => x.Place)
        .ToList()
       return data;
    }
}

Пока что так хорошо, data заселен.

Если нет круглых ссылок, жизнь велика. Однако, как только есть 2 Balance сущности с теми же Source или Place, тогда сериализация превращает более поздние Balance объекты самого верхнего списка, которые я возвращаю в ссылки Newtonsoft вместо их полнофункциональные объекты, поскольку они уже были сериализованы в свойстве Balances объектов Source или Place:

[{"$id":"1","BalanceID":4,"SourceID":2,"PlaceID":2 ...Omitted for clarity...},{"$ref":"4"}]

Проблема заключается в том, что клиент не знает, что делать с {$ref:4}, хотя мы, люди, понимаем, что происходит. В моем случае это означает, что я не могу использовать AngularJS для ng-repeat по всему моему списку балансов с этим JSON, потому что они не все true Balance объекты с атрибутом Balance для привязки. Я уверен, что есть много других прецедентов, которые будут иметь одинаковую проблему.

Я не могу отключить json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects, потому что много других вещей сломается (что хорошо описано в 100 других вопросах здесь и в других местах).

Есть ли лучший способ обхода этого решения, кроме как просматривать объекты в контроллере Web API и делать

Balance.Source.Balances = null;

всем свойствам навигации, чтобы разбить круговые ссылки? Потому что это тоже не так.

4b9b3361

Ответ 1

Да, использование PreserveReferencesHandling.Objects - действительно лучший способ сериализации графа объектов с циклическими ссылками, поскольку он создает самый компактный JSON и фактически сохраняет ссылочную структуру графа объектов. То есть, когда вы десериализуете JSON обратно на объекты (используя библиотеку, которая понимает нотацию $id и $ref), каждая ссылка на конкретный объект указывает на тот же экземпляр этого объекта, а не на несколько экземпляров с те же данные.

В вашем случае проблема заключается в том, что ваш парсер на стороне клиента не понимает нотации $id и $ref, созданной Json.Net, поэтому ссылки не решаются. Это можно исправить с помощью javascript-метода для восстановления ссылок на объекты после десериализации JSON. Ниже приведены примеры здесь и здесь.

Другая возможность, которая может работать в зависимости от вашей ситуации, - установить ReferenceLoopHandling на Ignore при сериализации вместо установки PreserveReferencesHandling в Objects. Однако это не идеальное решение. См. этот вопрос для подробного объяснения различий между использованием ReferenceLoopHandling.Ignore и PreserveReferencesHandling.Objects.