【发布时间】:2016-09-07 15:34:13
【问题描述】:
我有一个带有循环引用的大型对象图,我正在使用 Json.Net 对其进行序列化,以便在发送到客户端之前保留这些引用。在客户端,我使用了 Ken Smith 的JsonNetDecycle 的定制版本,这反过来又基于 Douglas Crockford 的cycle.js 在反序列化时恢复循环对象引用,并在将对象发回之前再次删除它们到服务器。在服务器端,我使用了一个自定义的 JsonDotNetValueProvider,类似于 this 问题中的那个,以便使用 Json.Net 而不是库存的 MVC5 JavaScriptSerializer。从服务器到客户端再返回,一切似乎都运行良好,Json 在往返过程中正常运行,但 MVC 无法正确反序列化对象图。
我已将问题追溯到此。当我将 JsonConvert.Deserialize 与具体类型参数一起使用时,一切正常,并且我得到了一个完整的对象图,其中子级和兄弟级正确地相互引用。但是,这不适用于 MVC ValueProvider,因为您不知道生命周期中那个时候的模型类型。 ValueProvider 只是应该以字典的形式提供值供 ModelBinder 使用。
在我看来,除非您可以为反序列化提供具体类型,否则对图中任何给定对象的第一次引用都可以反序列化,但对同一对象的任何后续引用都不会。那里有一个对象,但没有填写任何属性。
为了演示,我已经创建了最小的问题演示。在这个类中(使用 Json.Net 和 NUnit),我创建了一个对象图,并尝试以三种不同的方式对其进行反序列化。查看其他内联 cmets。
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using NUnit.Framework;
namespace JsonDotNetSerialization
{
[TestFixture]
public class When_serializing_and_deserializing_a_complex_graph
{
public Dude TheDude;
public Dude Gramps { get; set; }
public string Json { get; set; }
public class Dude
{
public List<Dude> Bros { get; set; }
public string Name { get; set; }
public Dude OldMan { get; set; }
public List<Dude> Sons { get; set; }
public Dude()
{
Bros = new List<Dude>();
Sons = new List<Dude>();
}
}
[SetUp]
public void SetUp()
{
Gramps = new Dude
{
Name = "Gramps"
};
TheDude = new Dude
{
Name = "The Dude",
OldMan = Gramps
};
var son1 = new Dude {Name = "Number one son", OldMan = TheDude};
var son2 = new Dude {Name = "Lil' Bro", OldMan = TheDude, Bros = new List<Dude> {son1}};
son1.Bros = new List<Dude> {son2};
TheDude.Sons = new List<Dude> {son1, son2};
Gramps.Sons = new List<Dude> {TheDude};
var jsonSerializerSettings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects
};
Json = JsonConvert.SerializeObject(TheDude, jsonSerializerSettings);
}
[Test]
public void Then_the_expected_json_is_created()
{
const string expected = @"{""$id"":""1"",""Bros"":[],""Name"":""The Dude"",""OldMan"":{""$id"":""2"",""Bros"":[],""Name"":""Gramps"",""OldMan"":null,""Sons"":[{""$ref"":""1""}]},""Sons"":[{""$id"":""3"",""Bros"":[{""$id"":""4"",""Bros"":[{""$ref"":""3""}],""Name"":""Lil' Bro"",""OldMan"":{""$ref"":""1""},""Sons"":[]}],""Name"":""Number one son"",""OldMan"":{""$ref"":""1""},""Sons"":[]},{""$ref"":""4""}]}";
Assert.AreEqual(expected, Json);
}
[Test]
public void Then_JsonConvert_can_recreate_the_original_graph()
{
// Providing a concrete type results in a complete graph
var dude = JsonConvert.DeserializeObject<Dude>(Json);
Assert.IsTrue(GraphEqualsOriginalGraph(dude));
}
[Test]
public void Then_JsonConvert_can_recreate_the_original_graph_dynamically()
{
dynamic dude = JsonConvert.DeserializeObject(Json);
// Calling ToObject with a concrete type results in a complete graph
Assert.IsTrue(GraphEqualsOriginalGraph(dude.ToObject<Dude>()));
}
[Test]
public void Then_JsonSerializer_can_recreate_the_original_graph()
{
var serializer = new JsonSerializer();
serializer.Converters.Add(new ExpandoObjectConverter());
var dude = serializer.Deserialize<ExpandoObject>(new JsonTextReader(new StringReader(Json)));
// The graph is still dynamic, and as a result, the second occurrence of "The Dude"
// (as the son of "Gramps") will not be filled in completely.
Assert.IsTrue(GraphEqualsOriginalGraph(dude));
}
private static bool GraphEqualsOriginalGraph(dynamic dude)
{
Assert.AreEqual("The Dude", dude.Name);
Assert.AreEqual("Gramps", dude.OldMan.Name);
Assert.AreEqual(2, dude.Sons.Count);
Assert.AreEqual("Number one son", dude.Sons[0].Name);
Assert.AreEqual("Lil' Bro", dude.Sons[0].Bros[0].Name);
// The dynamic graph will not contain this object
Assert.AreEqual("Lil' Bro", dude.Sons[1].Name);
Assert.AreEqual("Number one son", dude.Sons[1].Bros[0].Name);
Assert.AreEqual(1, dude.Sons[0].Bros.Count);
Assert.AreSame(dude.Sons[0].Bros[0], dude.Sons[1]);
Assert.AreEqual(1, dude.Sons[1].Bros.Count);
Assert.AreSame(dude.Sons[1].Bros[0], dude.Sons[0]);
// Even the dynamically graph forced through ToObject<Dude> will not contain this object.
Assert.AreSame(dude, dude.OldMan.Sons[0]);
return true;
}
}
}
JSON:
{
"$id":"1",
"Bros":[
],
"Name":"The Dude",
"OldMan":{
"$id":"2",
"Bros":[
],
"Name":"Gramps",
"OldMan":null,
"Sons":[
{
"$ref":"1"
}
]
},
"Sons":[
{
"$id":"3",
"Bros":[
{
"$id":"4",
"Bros":[
{
"$ref":"3"
}
],
"Name":"Lil' Bro",
"OldMan":{
"$ref":"1"
},
"Sons":[
]
}
],
"Name":"Number one son",
"OldMan":{
"$ref":"1"
},
"Sons":[
]
},
{
"$ref":"4"
}
]
}
我已经看到很多在自定义 ValueProvider 中使用 Json.Net 的示例,以便完全支持这种情况,但没有一个解决方案对我有用。我认为缺少的关键是我见过的所有示例都没有处理反序列化为动态或扩展对象和具有内部引用的交集。
【问题讨论】:
-
请发布您的 Json。 Json 没有引用。您所看到的引用只是可能对 some 序列化程序具有特殊含义的字符串。您可以使用 Json.NET 保留引用,如 Preserving Object References 所示。这会将
$id和$ref属性添加到链接对象 -
Json 完全包含在我发布的示例中。甚至还有一个测试来确保生成的 Json 符合我的预期。它位于 Then_the_expected_json_is_created 方法中的“预期”变量中。该 Json 中有“$ref”,其中儿子们互相指代,而 Gramps 指的是“The Dude”。另外,这个例子是使用 Json.Net,而 Json.Net 实际上是问题标题中的第一件事。
-
Json 文本不可读,我认为它也在第二页或第三页。无论如何,您的链接表明您使用的语法与 Json.NET 相同。您只需在反序列化期间设置正确的设置
-
如果有我没有尝试过的神奇设置组合,我很想看看。据我所知,这些设置在反序列化期间似乎并没有太大区别。但是,如果您希望 Json 在其中包含引用,则在序列化过程中肯定需要它们。我已经尝试了几乎所有我能找到的组合和设置,并且在反序列化方面似乎没有任何区别。
-
请提供一个使用反序列化设置的最小示例。我只是表明您在发表评论后确实使用了这些设置,但这也是两页后的内容。在我开始写答案之后。我没有看到它们用于反序列化,所以我仍然会发布答案并回家(这里是晚上 8 点)
标签: .net json serialization json.net