首页 > 解决方案 > Json Schema 中 $dynamicRef $dynamicAnchor 的解释(相对于 $ref 和 $anchor)

问题描述

有人可以解释JSON Schema 中 $dynamicRef 关键字的用途吗

有关实际使用,请参阅 JSON Schema 元模式本身。

它利用了 $dynamicAnchor 和 $dynamicRef。

核心架构看起来像这样

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/schema",
      ----   <snip>
    "$dynamicAnchor": "meta",      <-- first usage here
      ----   <snip>

    "allOf": [
        {"$ref": "meta/core"},
         ----   <snip>
    ]
      ----   <snip>
}

meta/coreallOf看起来像这样“包含”

{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://json-schema.org/draft/2020-12/meta/core",
      ----   <snip>

    "$dynamicAnchor": "meta",   <-- second usage here
    
      ----   <snip>
    "properties": {
          ----   <snip>

        "$defs": {
            "type": "object",
            "additionalProperties": { "$dynamicRef": "#meta" }    <-- reference here
              ----   <snip>
        }
    }
}

为什么这么复杂?它是如何工作的?即使我阅读了规范,我也无法真正理解它。

标签: jsonschema

解决方案


当扩展模式可能需要覆盖引用将解析到的位置时,使用动态引用。这在扩展递归模式(如元模式)中最常见,但也有其他用途。

这可能还没有意义,所以让我们从一个例子开始,说明为什么存在动态引用。此模式描述了一个递归树结构,其值为字符串。

{
  "$id": "https://example.com/schemas/string-tree",

  "type": "array",
  "items": {
    "anyOf": [
      { "type": "string" },
      { "$ref": "#" }
    ]
  }
}

这是一个针对该架构进行验证的实例。

["a", ["b", "c", ["d"], "e"]]

现在,假设我们要扩展此模式,以便每个分支最多有两个节点。您可能很想尝试以下架构。

{
  "$id": "https://example.com/schemas/bounded-string-tree",

  "$ref": "/schemas/string-tree",
  "maxItems": 2
}

但是,maxItems约束仅适用于树的根。in 中的递归引用/schemas/string-tree仍然指向它自己,它不限制节点的数量。

因此,["a", "b", "c"]由于存在三个节点,因此验证将按预期失败。但是,["a", ["b", "c", "d"]]不会失败,因为具有三个节点的分支仅针对/schemas/string-tree模式而不是/schemas/bounded-string-tree模式进行验证。

因此,为了扩展递归模式,我需要一种方法来允许扩展模式 ( /schemas/bound-string-tree) 更改扩展模式 ( /schemas/string-tree) 中的引用目标。动态引用提供了这种机制。

{
  "$id": "https://example.com/schemas/base-string-tree",
  "$dynamicAnchor": "branch",

  "type": "array",
  "items": {
    "anyOf": [
      { "type": "string" },
      { "$dynamicRef": "#branch" }
    ]
  }
}

在这种情况下,动态引用和锚点与常规引用和锚点的工作方式相同,只是现在如果需要,可以通过扩展模式覆盖引用。如果没有扩展模式,它将转到这个动态锚点。如果有一个扩展模式声明了一个匹配的动态锚,它将覆盖这个更改动态引用解析到的位置。

{
  "$id": "https://example.com/schemas/bounded-string-tree",
  "$dynamicAnchor": "branch",

  "$ref": "/schemas/base-string-tree",
  "maxItems": 2
}

通过在 中设置“分支”动态锚点/schemas/bounded-string-tree,我们有效地覆盖了任何未来对“分支”的动态引用以解析到该位置。

现在,将按预期["a", ["b", "c", "d"]]失败验证。/schema/bounded-string-tree

您可能还听说过$recursiveRefJSON Schema 2019-09。这是动态引用的前一个化身,仅对扩展本示例中的递归模式有用。与递归引用不同,动态引用允许您在模式中设置多个扩展点。让我们将我们的示例更进一步,看看为什么它是有用的。

假设我们想要一个描述树的模式,但我们希望扩展模式能够覆盖树的叶子的模式。例如,我们可能想要一棵具有多个叶节点而不是字符串的树。我们可以使用动态引用来允许覆盖叶子。

{
  "$id": "https://example.com/schemas/base-tree",
  "$dynamicAnchor": "branch",

  "type": "array",
  "items": {
    "anyOf": [
      { "$dynamicRef": "#leaf" },
      { "$dynamicRef": "#branch" }
    ]
  },

  "$defs": {
    "leaf": {
      "$dynamicAnchor": "leaf",
      "type": "string"
    }
  }
}

现在我们有两个扩展点,可以创建一个有界数树。

{
  "$id": "https://example.com/schemas/bounded-number-tree",
  "$dynamicAnchor": "branch",

  "$ref": "/schemas/base-tree",
  "maxItems": 2,

  "$defs": {
    "$dynamicAnchor": "leaf",
    "type": "number"
  }
}

动态引用还有一些更复杂的地方,我现在不会讨论。希望这足以说明为什么存在这种复杂的机制以及何时想要使用它。我希望它也让它看起来不那么复杂。


推荐阅读