我有一个简单的父子关系模型,如下所示:
public class Parent
{
public Parent(int id)
{
Id = id;
}
public int Id { get; private set; }
public IList<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public Parent Parent { get; set; }
}
我创建了一个小对象图,由共享同一父对象的两个子对象的列表组成:
var parent = new Parent(1);
var child1 = new Child {Parent = parent};
var child2 = new Child {Parent = parent};
parent.Children.Add(child1);
parent.Children.Add(child2);
var data = new List<Child> {child1, child2};
接下来,我使用SerializeObject
:将其序列化
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);
据我所见,生成的json看起来不错:
{
"$id": "1",
"$values": [
{
"$id": "2",
"Parent": {
"$id": "3",
"Id": 1,
"Children": {
"$id": "4",
"$values": [
{
"$ref": "2"
},
{
"$id": "5",
"Parent": {
"$ref": "3"
}
}
]
}
}
},
{
"$ref": "5"
}
]
}
然而,当我反序列化json时,我没有得到预期的对象,因为对于第二个子级,Parent属性为null,导致第二个断言失败:
var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);
Debug.Assert(data2[0].Parent != null);
Debug.Assert(data2[1].Parent != null);
如果没有Parent
的构造函数,则不会出现此问题,并且第二个子对象的Parent属性具有预期值。
有什么想法吗?
参数化构造函数不能很好地使用PreserveReferencesHandling
,因为存在一个固有的鸡和蛋问题:在反序列化程序需要创建对象时,传递给构造函数的必要信息可能无法从JSON加载。因此,它需要有一种方法来创建一个空对象,然后稍后填充适当的信息。
这是一个已知的限制,并记录在案:
注意:
通过非默认构造函数设置值时,不能保留引用。对于非默认构造函数,必须在父值之前创建子值,以便将它们传递到构造函数中,从而无法跟踪引用。ISerializable
类型是一个类的示例,该类的值由非默认构造函数填充,不适用于PreserveReferencesHandling
。
要解决模型的问题,可以在Parent
类中创建一个私有的无参数构造函数,并用[JsonConstructor]
标记它。然后用[JsonProperty]
标记Id
属性,这将允许Json。Net使用私有setter。所以你会有:
public class Parent
{
public Parent(int id)
{
Id = id;
}
[JsonConstructor]
private Parent()
{ }
[JsonProperty]
public int Id { get; private set; }
public IList<Child> Children { get; set; } = new List<Child>();
}
顺便说一句,由于对象之间没有共享您的列表,因此可以使用PreserveReferencesHandling.Objects
而不是PreserveReferencesHandling.All
。这将使JSON变小一点,但仍将保留您关心的引用。
在此处进行演示:https://dotnetfiddle.net/cLk9DM
我还没有测试过这个,但我有一种感觉,如果你把设置放在serializer和deserializer:
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.All
};
var json = Newtonsoft.Json.JsonConvert.SerializeObject(data, Formatting.Indented, settings);
var data2 = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<Child>>(json, settings);
第二次尝试:
我重现了这个问题,然后将Parent
的构造函数改为无参数,将Id
的属性改为可设置,然后它就工作了。不知道为什么,但我想Newtonsoft选择如何构建东西的方式有一些缺陷,尤其是因为PreserveReferencesHandling
的顺序很重要。
public class Parent
{
public int Id { get; set; }
public IList<Child> Children { get; set; } = new List<Child>();
}
和
var parent = new Parent { Id = 1 };