你好吗"真的"使用Newtonsoft.Json序列化循环引用对象?

时间:2014-10-17 23:26:40

标签: c# json entity-framework asp.net-web-api json.net

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

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

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

Model

在我的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已填充。

如果没有循环引用,那么生命就是盛大的。但是,只要有BalanceSource实体具有相同的PlaceBalance,那么序列化就会转换最顶层列表的后Balances个对象{ 39; m返回到Newtonsoft引用而不是它们的完整对象,因为它们已经在SourcePlace对象的[{"$id":"1","BalanceID":4,"SourceID":2,"PlaceID":2 ...Omitted for clarity...},{"$ref":"4"}] 属性中序列化了:

{$ref:4}

问题在于,即使我们人类了解了正在发生的事情,客户也不知道如何处理ng-repeat。在我的情况下,这意味着我不能使用AngularJS在我的整个余额列表中Balance使用此JSON,因为它们不是所有具有Balance属性的真json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects个对象绑定。我确信还有很多其他用例会出现同样的问题。

我无法关闭Balance.Source.Balances = null; 因为很多其他事情会破坏(这在其他100个问题中有详细记录)。

除了通过Web API控制器中的实体并执行

之外,还有更好的解决方法吗?
{{1}}

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

2 个答案:

答案 0 :(得分:19)

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

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

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

答案 1 :(得分:0)

我编写了一个最小程序来对此进行测试。这是我的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
        }
    }
}
相关问题