我在使用Newtonsoft.Json从我的ASP.NET Web API控制器中正确序列化某些数据时遇到问题。
以下是认为正在进行的事情 - 如果我错了,请纠正我。在某些情况下(特别是当数据中没有任何循环引用时)一切都像你期望的那样 - 一系列填充的对象被序列化并返回。如果我引入的数据导致模型中的循环引用(如下所述,甚至设置PreserveReferencesHandling.Objects
),则只有通过循环引用导致第一个对象的列表元素才会以客户端的方式序列化可以"与"一起工作。 "元素导致"如果在将内容发送到序列化程序之前以不同方式排序,则可以是数据中的任何元素,但至少有一个元素将以客户端可以"使用"的方式序列化。空对象最终被序列化为Newtonsoft引用({$ref:X}
)。
例如,如果我有一个EF模型,其导航属性如下所示:
在我的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
已填充。
如果没有循环引用,那么生命就是盛大的。但是,只要有Balance
个Source
实体具有相同的Place
或Balance
,那么序列化就会转换最顶层列表的后Balances
个对象{ 39; m返回到Newtonsoft引用而不是它们的完整对象,因为它们已经在Source
或Place
对象的[{"$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}}
要打破循环引用的所有导航属性?因为这似乎也不对。
答案 0 :(得分:19)
是的,使用PreserveReferencesHandling.Objects
实际上是使用循环引用序列化对象图的最佳方法,因为它生成最紧凑的JSON并且它实际上保留了对象图的引用结构。也就是说,当您将JSON反序列化回对象时(使用理解$id
和$ref
表示法的库),对特定对象的每个引用都将指向该对象的同一实例,而不是具有相同数据的多个实例。
在您的情况下,问题是您的客户端解析器不理解Json.Net生成的$id
和$ref
表示法,因此引用未被解析。这可以通过使用javascript方法在反序列化JSON后重建对象引用来修复。有关示例,请参阅here和here。
根据您的具体情况,可能有效的另一种可能性是在序列化时将ReferenceLoopHandling
设置为Ignore
,而不是将PreserveReferencesHandling
设置为Objects
。但这不是一个完美的解决方案。有关使用ReferenceLoopHandling.Ignore
和PreserveReferencesHandling.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
}
}
}