反序列化关系模型的 Newtonsoft.Json 问题
Posted
技术标签:
【中文标题】反序列化关系模型的 Newtonsoft.Json 问题【英文标题】:Newtonsoft.Json issue with deserialising relational model 【发布时间】:2020-07-27 00:59:41 【问题描述】:我有一个简单的关系模型,其父子关系如下:
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 属性为空,导致第二个断言失败:
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 而不是在反序列化上,所以它不知道如何读取 $refs? 请分享生成的准确 JSON。另外,你读过newtonsoft.com/json/help/html/PreserveObjectReferences.htm 吗? 我更新了代码以在反序列化时也使用 PreserveReferencesHandling。但是,这没有任何效果我还包括了生成的 JSON 【参考方案1】:参数化构造函数不能很好地与PreserveReferencesHandling
一起工作,因为存在一个先天先有鸡的问题:在反序列化程序需要创建目的。所以需要有一种方法让它创建一个空对象,然后再填写适当的信息。
这是一个已知的限制,是documented:
注意: 通过非默认构造函数设置值时,无法保留引用。对于非默认构造函数,必须在父值之前创建子值,以便将它们传递给构造函数,从而无法跟踪引用。
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
【讨论】:
感谢您的详细解释。我自己也在考虑这些属性,但是我对使用特定序列化程序的提示“污染”我的数据模型感到不高兴。您对此有何看法? 哈,好吧,我猜我对这些事情比较务实。如果添加几个属性是我完成工作的唯一障碍,并且不会以任何方式干扰我的设计,那么我将采用这些属性。然而,这只是我。您最终必须决定什么对您和您的代码最有利,因为您将是维护它的人。我可以理解为什么您可能不想在模型库中依赖 Json.Net。在这种情况下,我建议您根本不要序列化您的模型——而是将它们映射到 DTO 并对其进行序列化。【参考方案2】:我还没有测试过这个,但我觉得如果你把设置放入序列化器和反序列化器中它会解决它:
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);
【讨论】:
我更新了帖子中的代码片段,以便在反序列化时也使用 PreserveReferencesHandling 设置。但是,does没有任何作用/。 嗯。很奇怪 - 正如你所说的 json 看起来不错【参考方案3】:第二次尝试:
我重现了这个问题,然后把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 ;
【讨论】:
是的,我也想通了。但是,我确实更喜欢我的只读模型,其中属性仅在构建时才获得价值。 如果您希望您的模型是纯的,那么您是否考虑过不让它们相互引用?你可以在Parent
上有一个getter,它接收孩子并返回一个包装Child
并引用其Parent
的不同类,这将涵盖所有用例以及以正常方式可序列化。跨度>
就实际问题而言,我认为这可能是 Newtonsoft 中的一个错误,因此如果还没有问题,可能值得打开一个问题。 github.com/JamesNK/Newtonsoft.Json/issues
已提交github.com/JamesNK/Newtonsoft.Json/issues/2315
我不确定我是否完全理解您提出的使模型纯净的建议。你能再解释一下吗?以上是关于反序列化关系模型的 Newtonsoft.Json 问题的主要内容,如果未能解决你的问题,请参考以下文章