Как вы "действительно" Сериализовать круговые ссылки объектов с помощью 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;
всем свойствам навигации, чтобы разбить круговые ссылки? Потому что это тоже не так.
Ответы
Ответ 1
Да, использование PreserveReferencesHandling.Objects
- действительно лучший способ сериализации графа объектов с циклическими ссылками, поскольку он создает самый компактный JSON и фактически сохраняет ссылочную структуру графа объектов. То есть, когда вы десериализуете JSON обратно на объекты (используя библиотеку, которая понимает нотацию $id
и $ref
), каждая ссылка на конкретный объект указывает на тот же экземпляр этого объекта, а не на несколько экземпляров с те же данные.
В вашем случае проблема заключается в том, что ваш парсер на стороне клиента не понимает нотации $id
и $ref
, созданной Json.Net, поэтому ссылки не решаются. Это можно исправить с помощью javascript-метода для восстановления ссылок на объекты после десериализации JSON. Ниже приведены примеры здесь и здесь.
Другая возможность, которая может работать в зависимости от вашей ситуации, - установить ReferenceLoopHandling
на Ignore
при сериализации вместо установки PreserveReferencesHandling
в Objects
. Однако это не идеальное решение. См. этот вопрос для подробного объяснения различий между использованием ReferenceLoopHandling.Ignore
и PreserveReferencesHandling.Objects
.