问题描述
我使用Newtonsoft.Json从ASP.NET Web API控制器中正确获取一些数据的序列化问题。
这是我认为正在发生的事情 - 如果我错了,请更正我。在某些情况下(特别是在数据中没有任何循环引用)时,一切都可以像预期一样工作,一系列填充对象被序列化并返回。如果引入了在模型中引用循环引用的数据(如下所述,甚至使用 PreserveReferenceHandling.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< Balance> GetStartingBalances()
{
using(MyContext db = new MyContext())
{
var data = db.Balances
.Include(x => x。来源)
.Include(x => x.Place)
.ToList()
返回数据;
}
}
到目前为止这么好,数据被填充。
如果没有循环引用,生命是盛大的。但是,一旦有$ 平衡相同的来源或 Place ,那么序列化会将我返回到Newtonsoft引用而不是其完整对象的最上面的列表中的余额对象变为已经序列化了余额属性的源或 Place 对象:
[{$ id:1,BalanceID:4,SourceID :2,PlaceID:2 ...为了清楚起见省略...},{$ ref:4}]
这个问题是客户端不知道如何处理 {$ ref:4} 即使我们人类了解什么继续在我的情况下,这意味着我不能使用AngularJS到 ng-repeat 在我的整个余额列表中使用这个JSON,因为它们不是全部余额要绑定的余额属性的对象。我确定有很多其他用例会有同样的问题。
我无法关闭 json.SerializerSettings .PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects 因为很多其他的事情会破裂(这里有100个其他问题在这里和其他地方有详细的记录)。
除了通过Web API控制器中的实体以及执行
<$ p还有更好的解决方法$ p> Balance.Source.Balances = null;
所有的导航属性都可以打破循环引用?
是的,使用 PreserveReferencesHandling.Objects 是使用循环引用序列化对象图的最佳方法,因为它生成最紧凑的JSON,它实际上保留了对象图的参考结构。也就是说,当您将JSON反序列化为对象(使用了解 $ id 和 $ ref 符号的库),对特定对象的每个引用将指向该对象的相同实例,而不是具有相同数据的多个实例。
在您的情况下,问题是您的客户端解析器不明白 $ id 和 $ ref Json.Net生成的符号,所以引用没有被解决。这可以通过使用javascript方法在反序列化JSON后重建对象引用来修复。请参阅和 c>和 PreserveReferencesHandling.Objects 。
I'm having a problem getting some data serialized correctly from my ASP.NET Web API controller using Newtonsoft.Json.
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}).
For example, if I have an EF model complete with navigation properties that looks like this:
In my global.asax:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
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; } }
So far so good, data is populated.
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"}]
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.
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).
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.
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.
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.
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序列化循环引用对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!