Json Schema 中 $dynamicRef $dynamicAnchor 的解释(相对于 $ref 和 $anchor)

Explanation of $dynamicRef $dynamicAnchor in Json Schema (as opposed to $ref and $anchor)

有人可以解释 $dynamicRef keyword in JSON Schema

的目的吗

对于实际使用,请参阅 JSON 架构元架构本身。

它利用了 $dynamicAnchor 和 $dynamicRef。

Core schema 看起来像这样

{
    "$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>
        }
    }
}

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

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

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

{
  "$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 约束只适用于树的根。 /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 进行验证。

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

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

{
  "$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": {
    "leaf": {
      "$dynamicAnchor": "leaf",
      "type": "number"
    }
  }
}

动态引用还有一些更复杂的地方,我暂时不谈。希望这足以说明存在这种复杂机制的原因以及您希望何时使用它。我希望它也让它看起来不那么复杂。