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

问题描述 投票:16回答:2

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

下面是我的情况 认为 是怎么回事--如果我错了,请纠正我。 在某些情况下(特别是当数据中没有任何循环引用时),一切都像你所期望的那样工作--一个填充对象的列表被序列化并返回。 如果我引入的数据在模型中导致循环引用(如下所述,即使是用 PreserveReferencesHandling.Objects set),只有列表中第一个循环引用对象之前的元素才会以客户端可以 "使用 "的方式被序列化。 前面的元素 "可以是数据中的任何一个元素,如果在发送东西到序列化器之前,它的排序方式不同的话,但至少有一个元素会以客户端可以 "使用 "的方式序列化。 空对象最终会被序列化为Newtonsoft引用({$ref:X}).

例如,如果我有一个EF模型,完整的导航属性,看起来像这样。

Model

在我的 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.Source)
        .Include(x => x.Place)
        .ToList()
       return data;
    }
}

到目前为止一切正常 data 是有人口的。

如果没有循环引用,生活是盛大的。 然而,只要有2 Balance 相同的实体 SourcePlace,然后序列化将后面的 Balance 的对象,我将它们返回到Newtonsoft的引用中,而不是它们的完整对象,因为它们已经在 Balances 的财产 SourcePlace 对象。

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

到所有的导航属性以打破循环引用? 因为这似乎也不对。

c# json entity-framework asp.net-web-api json.net
2个回答
20
投票

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

在您的案例中,问题在于您的客户端解析器不理解 $id$ref Json.Net产生的符号,所以引用没有被解析。 这可以通过使用javascript方法在反序列化JSON后重建对象引用来解决。请看 此处此处 的例子。

另一种可能的做法是根据你的情况,在你的电脑上设置 ReferenceLoopHandlingIgnore 而不是在序列化时设置 PreserveReferencesHandlingObjects. 这虽然不是一个完美的解决方案。请看 这个问题 详细解释使用 ReferenceLoopHandling.IgnorePreserveReferencesHandling.Objects.


0
投票

我写了一个最小的程序来测试这个。这是我的github。https:/github.comassafwo1TestSerializeJsonObjects。. 这是代码。

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
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.