使用 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);