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

时间:2022-01-06 20:43:11

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

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

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}).

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

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

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

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

In my global.asax:

在我的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):

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

[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):

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

[{"$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.

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

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).

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

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

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

Balance.Source.Balances = null;

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

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

1 个解决方案

#1


16  

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.

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

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.

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

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.

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

#1


16  

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.

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

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.

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

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.

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