你怎么“真的"?使用 Newtonsoft.Json 序列化循环引用对象?

How Do You quot;Reallyquot; Serialize Circular Referencing Objects With Newtonsoft.Json?(你怎么“真的?使用 Newtonsoft.Json 序列化循环引用对象?)

本文介绍了你怎么“真的"?使用 Newtonsoft.Json 序列化循环引用对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm having a problem getting some data serialized correctly from my ASP.NET Web API controller using Newtonsoft.Json.

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

Here's what I think is going on - please correct me if I'm wrong. Under certain circumstances (specifically when there aren't any circular references in the data) everything works just like you'd expect - a list of populated objects gets serialized and returned. If I introduce data that causes a circular reference in the model (described below, and even with PreserveReferencesHandling.Objects set) only the elements of the list leading up to the first object with a circular reference get serialized in a way that the client can "work with". The "elements leading up to" can be any of the elements in the data if it's ordered differently before sending things to the serializer, but at least one will be serialized in a way the client can "work with". The empty objects end up being serialized as Newtonsoft references ({$ref:X}).

例如,如果我有一个包含如下导航属性的 EF 模型:

For example, if I have an EF model complete with navigation properties that looks like this:

在我的 global.asax 中:

In my global.asax:

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

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

Here's the fundamental query I'm doing using Entity Framework (lazy-loading is off so there aren't any proxy classes here):

[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 已填充.

So far so good, data is populated.

如果没有循环引用,生活就是伟大的.但是,只要有 2 个 Balance 实体具有相同的 SourcePlace,那么序列化就会转向后面的 Balance 对象而不是它们的完整对象,因为它们已经在 SourceBalances 属性中序列化或 Place 对象:

If there are no circular references, life is grand. However, as soon as there are 2 Balance entities with the same Source or Place, then the serialization turns the later Balance objects of the top-most list that I'm returning into Newtonsoft references instead of their full-fledged objects because they were already serialized in the Balances property of the Source or Place object(s):

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

这样做的问题是客户端不知道如何处理 {$ref:4},即使我们人类理解正在发生的事情.在我的情况下,这意味着我不能使用 AngularJS 来 ng-repeat 在我的整个余额列表中使用这个 JSON,因为它们并非都是真正的 Balance 对象Balance 属性绑定.我敢肯定还有很多其他用例会遇到同样的问题.

The problem with this is that the client doesn't know what to do with {$ref:4} even though we humans understand what's going on. In my case, this means that I cannot use AngularJS to ng-repeat over my entire list of Balances with this JSON, because they aren't all true Balance objects with a Balance property to bind. I'm sure there are tons of other use-cases that would have the same problem.

我无法关闭 json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects 因为很多其他事情都会中断(这在此处和其他地方的 100 个其他问题中有详细记录).

I can't turn off the json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects because lots of other things would break (which is well-documented in 100 other questions here and elsewhere).

除了遍历 Web API 控制器中的实体并执行此操作之外,是否有更好的解决方法

Is there a better workaround for this apart from going through the entities in the Web API controller and doing

Balance.Source.Balances = null;

要打破循环引用的所有导航属性?因为那似乎也不对.

to all of the navigation properties to break the circular references? Because THAT doesn't seem right either.

推荐答案

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

Yes, using PreserveReferencesHandling.Objects is really the best way to serialize an object graph with circular references, because it produces the most compact JSON and it actually preserves the reference structure of the object graph. That is, when you deserialize the JSON back to objects (using a library that understands the $id and $ref notation), each reference to a particular object will point to the same instance of that object, rather than having multiple instances with the same data.

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

In your case the problem is that your client side parser does not understand the $id and $ref notation produced by Json.Net, so the references are not being resolved. This can be fixed by using a javascript method to reconstruct the object references after deserializing the JSON. See here and here for examples.

根据您的情况,另一种可能可行的方法是在序列化时将 ReferenceLoopHandling 设置为 Ignore 而不是将 PreserveReferencesHandling 设置为 对象.虽然这不是一个完美的解决方案.请参阅 this question 了解使用 ReferenceLoopHandling.Ignore 之间的差异的详细说明PreserveReferencesHandling.Objects.

Another possibility which might work, depending on your situation, is to set ReferenceLoopHandling to Ignore when serializing instead of setting PreserveReferencesHandling to Objects. This is not a perfect solution though. See this question for a detailed explanation of the differences between using ReferenceLoopHandling.Ignore and PreserveReferencesHandling.Objects.

这篇关于你怎么“真的"?使用 Newtonsoft.Json 序列化循环引用对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:你怎么“真的"?使用 Newtonsoft.Json 序列化循环引用对象?