您如何“真正”使用 Newtonsoft.Json 序列化循环引用对象?
Posted
技术标签:
【中文标题】您如何“真正”使用 Newtonsoft.Json 序列化循环引用对象?【英文标题】:How Do You "Really" Serialize Circular Referencing Objects With Newtonsoft.Json? 【发布时间】:2014-12-13 14:39:32 【问题描述】:我在使用 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;
这是我使用实体框架执行的基本查询(延迟加载已关闭,因此这里没有任何代理类):
[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 个具有相同 Source
或 Place
的 Balance
实体,那么序列化就会将我返回到 Newtonsoft 引用而不是它们的最顶层列表的后面的 Balance
对象成熟的对象,因为它们已经在Source
或Place
对象的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;
要打破循环引用的所有导航属性?因为那似乎也不对。
【问题讨论】:
【参考方案1】:是的,使用PreserveReferencesHandling.Objects
确实是使用循环引用序列化对象图的最佳方式,因为它生成最紧凑的 JSON,并且实际上保留了对象图的引用结构。也就是说,当您将 JSON 反序列化回对象时(使用理解 $id
和 $ref
表示法的库),对特定对象的每个引用都将指向该对象的同一个实例,而不是具有多个实例相同的数据。
在您的情况下,问题是您的客户端解析器不理解 Json.Net 生成的 $id
和 $ref
表示法,因此无法解析引用。这可以通过在反序列化 JSON 后使用 javascript 方法来重建对象引用来解决。有关示例,请参见 here 和 here。
根据您的情况,另一种可能可行的方法是在序列化时将ReferenceLoopHandling
设置为Ignore
,而不是将PreserveReferencesHandling
设置为Objects
。虽然这不是一个完美的解决方案。有关使用 ReferenceLoopHandling.Ignore
和 PreserveReferencesHandling.Objects
之间差异的详细说明,请参阅 this question。
【讨论】:
【参考方案2】:我写了一个最小的程序来测试这个。这是我的github: https://github.com/assafwo1/TestSerializeJsonObjects。这是代码:
using Newtonsoft.Json;
using System.Diagnostics;
namespace TestSerializeJsonObjects
class Program
public class Node
public Node Next get; set;
static void Main(string[] args)
// create new node
var head = new Node();
// point its "next" field at itself
head.Next = head;
// this is now the smallest circular reference data structure possible
// assert that head next is head
Debug.Assert(head.Next == head);
// save to string
var s = JsonConvert.SerializeObject(head, new JsonSerializerSettings
PreserveReferencesHandling = PreserveReferencesHandling.Objects
);
// get from string
var head2 = JsonConvert.DeserializeObject<Node>(s, new JsonSerializerSettings
PreserveReferencesHandling = PreserveReferencesHandling.Objects
);
// assert that head2 next is head2
Debug.Assert(head2.Next == head2);
// done
【讨论】:
嗨,阿萨夫,沙洛姆,欢迎来到 SO。很高兴您花时间回答问题,但最好为您的代码添加解释,而不仅仅是从您的 Github 中粘贴代码……我建议您编辑您的代码并添加一些解释。以上是关于您如何“真正”使用 Newtonsoft.Json 序列化循环引用对象?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 C# 进行条件序列化 - NewtonSoft.Json
如何使用 Newtonsoft.JSON 解析 JSON 响应? [复制]
如何使用 Newtonsoft.Json 反序列化 JSON 数组