Newtonsoft.Json关于取消关系模型序列化的问题



我有一个简单的父子关系模型,如下所示:

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 };

相关内容

  • 没有找到相关文章

最新更新