你如何“真正”使用Newtonsoft.Json序列化循环引用对象?

我在使用Newtonsoft.Json从我的ASP.NET Web API控制器正确地序列化数据时遇到问题。

这就是我的想法 – 如果我错了,请纠正我。 在某些情况下(特别是当数据中没有任何循环引用时)一切都像您期望的那样工作 – 填充对象列表被序列化并返回。 如果我在模型中引入导致循环引用的数据(如下所述,甚至设置了PreserveReferencesHandling.Objects ),则只有通过循环引用导致第一个对象的列表元素才能以客户端可以“序列化”的方式进行序列化。与“合作”。 如果在将数据发送到序列化程序之前以不同方式排序,则“导致数据”的元素可以是数据中的任何元素,但至少有一个元素将以客户端可以“使用”的方式进行序列化。 空对象最终被序列化为Newtonsoft引用( {$ref:X} )。

例如,如果我有一个EF模型,其导航属性如下所示:

模型

在我的global.asax中:

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

这是我使用Entity Framework进行的基本查询(延迟加载已关闭,因此这里没有任何代理类):

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

到目前为止一直很好, data填充。

如果没有循环引用,那么生命就是盛大的。 但是,只要有2个Balance实体具有相同的SourcePlace ,那么序列化会将我返回的最顶层列表的后续Balance对象转换为Newtonsoft引用而不是它们的完整对象,因为它们已经是在SourcePlace对象的Balances属性中序列化:

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

这个问题是客户端不知道如何处理{$ref:4}即使我们人类了解正在发生的事情。 就我而言,这意味着我无法使用AngularJS对我的整个余额列表进行ng-repeat与此JSON,因为它们并非都是具有要绑定的Balance属性的真实Balance对象。 我确信还有很多其他用例会出现同样的问题。

我无法关闭json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects因为很多其他东西都会破坏(这里和其他地方的100个其他问题都有详细记录)。

除了通过Web API控制器中的实体并执行此操作之外,还有更好的解决方法吗?

 Balance.Source.Balances = null; 

要打破循环引用的所有导航属性? 因为这似乎也不正确。

是的,使用PreserveReferencesHandling.Objects实际上是使用循环引用序列化对象图的最佳方法,因为它生成最紧凑的JSON并且它实际上保留了对象图的引用结构。 也就是说,当您将JSON反序列化回对象时(使用理解$id$ref表示法的库),对特定对象的每个引用都将指向该对象的同一实例,而不是具有相同的多个实例数据。

在您的情况下,问题是您的客户端解析器不理解Json.Net生成的$id$ref表示法,因此引用未被解析。 这可以通过使用javascript方法在反序列化JSON后重建对象引用来修复。 请参阅此处和此处的示例。

根据您的具体情况,另一种可能的工作方式是在序列化时将ReferenceLoopHandling设置为Ignore ,而不是将PreserveReferencesHandling设置为Objects 。 但这不是一个完美的解决方案。 有关使用ReferenceLoopHandling.IgnorePreserveReferencesHandling.Objects之间差异的详细说明,请参阅此问题 。