使用 Json.Net 解析两个语义相同的层次结构文档并失败
Using Json.Net to parse two semantically identical hierarchy documents and failing
我有两个 JSON 文档,它们代表相同的层次结构和内容。我在这两个文档之间看到的唯一区别是键值对的顺序不同。一份文件按我的预期解析,而另一份文件没有。
我正在使用 "Preserve References Handling" 所以节点应该引用它的父节点。 (测试中的变量 "hierarchyTwoNode" 是未设置父 属性 的文档)。我已经包含了一个测试 (can be found here) 来证明这一点。这是工作的简化版本 JSON:
{
"Root": {
"$id": "1",
"Id": "1472459628771017730",
"Type": "cras",
"Content": {
"Name": "lorem"
},
"Parent": null,
"Children": [
{
"$id": "2",
"Id": "1472459628812960771",
"Type": "morbi",
"Content": {
"Name": "ipsum dolor"
},
"Parent": {
"$ref": "1"
}
}
]
}
}
失败的JSON:
{
"Root": {
"Parent": null,
"$id": "1",
"Children": [
{
"Parent": {
"$ref": "1"
},
"$id": "2",
"Content": {
"Name": "ipsum dolor"
},
"Type": "morbi",
"Id": "1472459628812960771"
}
],
"Content": {
"Name": "lorem"
},
"Type": "cras",
"Id": "1472459628771017730"
}
}
有人可以告诉我发生了什么事吗?
Could someone give me an idea about what is happening?
本质上,发生的事情是在 JSON 字符串之一上,您的元数据属性被放置在 之后 第一个实际 属性.
所有$xxx
属性都是元数据,必须放在对象/子对象的开头。
此限制的原因如下:
if we look at the internals of JSON.Net,在执行元数据查找时,我们可以看到,一旦我们读取了一个不是元数据的 属性,我们就停止了元数据查找。
如果 JSON 文件非常大,我只能公平地假设它用于计算和内存优化。
要使您的代码正常工作,只需将所有以 $
开头的属性放在对象的开头,它应该会像魅力一样工作。
正如@Fabio Salvalai 正确推断的那样,Json.Net 通常期望任何元数据属性(例如 $id
或 $type
)首先出现在每个对象中,以实现反序列化的最佳效率。如果元数据没有首先出现,则 Json.Net 假定它不存在。这就是为什么在重新排序属性时会得到不同结果的原因。
幸运的是,Json.Net 提供了一个 MetadataPropertyHandling
设置来允许它处理这种情况。如果您将 MetadataPropertyHandling
设置为 ReadAhead
它应该可以解决您的问题。请注意,如果您的 JSON 很大,这可能会对性能产生影响。
JsonSerializerSettings settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
};
var hierarchyOne = JsonConvert.DeserializeObject<Hierarchy>(HierarchyOne, settings);
var hierarchyTwo = JsonConvert.DeserializeObject<Hierarchy>(HierarchyTwo, settings);
我有两个 JSON 文档,它们代表相同的层次结构和内容。我在这两个文档之间看到的唯一区别是键值对的顺序不同。一份文件按我的预期解析,而另一份文件没有。
我正在使用 "Preserve References Handling" 所以节点应该引用它的父节点。 (测试中的变量 "hierarchyTwoNode" 是未设置父 属性 的文档)。我已经包含了一个测试 (can be found here) 来证明这一点。这是工作的简化版本 JSON:
{
"Root": {
"$id": "1",
"Id": "1472459628771017730",
"Type": "cras",
"Content": {
"Name": "lorem"
},
"Parent": null,
"Children": [
{
"$id": "2",
"Id": "1472459628812960771",
"Type": "morbi",
"Content": {
"Name": "ipsum dolor"
},
"Parent": {
"$ref": "1"
}
}
]
}
}
失败的JSON:
{
"Root": {
"Parent": null,
"$id": "1",
"Children": [
{
"Parent": {
"$ref": "1"
},
"$id": "2",
"Content": {
"Name": "ipsum dolor"
},
"Type": "morbi",
"Id": "1472459628812960771"
}
],
"Content": {
"Name": "lorem"
},
"Type": "cras",
"Id": "1472459628771017730"
}
}
有人可以告诉我发生了什么事吗?
Could someone give me an idea about what is happening?
本质上,发生的事情是在 JSON 字符串之一上,您的元数据属性被放置在 之后 第一个实际 属性.
所有$xxx
属性都是元数据,必须放在对象/子对象的开头。
此限制的原因如下:
if we look at the internals of JSON.Net,在执行元数据查找时,我们可以看到,一旦我们读取了一个不是元数据的 属性,我们就停止了元数据查找。
如果 JSON 文件非常大,我只能公平地假设它用于计算和内存优化。
要使您的代码正常工作,只需将所有以 $
开头的属性放在对象的开头,它应该会像魅力一样工作。
正如@Fabio Salvalai 正确推断的那样,Json.Net 通常期望任何元数据属性(例如 $id
或 $type
)首先出现在每个对象中,以实现反序列化的最佳效率。如果元数据没有首先出现,则 Json.Net 假定它不存在。这就是为什么在重新排序属性时会得到不同结果的原因。
幸运的是,Json.Net 提供了一个 MetadataPropertyHandling
设置来允许它处理这种情况。如果您将 MetadataPropertyHandling
设置为 ReadAhead
它应该可以解决您的问题。请注意,如果您的 JSON 很大,这可能会对性能产生影响。
JsonSerializerSettings settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
};
var hierarchyOne = JsonConvert.DeserializeObject<Hierarchy>(HierarchyOne, settings);
var hierarchyTwo = JsonConvert.DeserializeObject<Hierarchy>(HierarchyTwo, settings);