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

Can somebody explain the purpose of the $dynamicRef keyword in JSON Schema

For an actual use, see the JSON Schema meta schema itself.

It makes use of $dynamicAnchor and $dynamicRef.

Core schema looks like this

{
    "$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/core (which is "included" by allOf looks like this

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

Why is it so complicated? And how does it work? Even though I read the specification I cannot really make sense of it.



Solution 1:[1]

A dynamic reference is used when an extending schema might need to override where a reference will resolve to. This is most common in extending recursive schemas (like the meta-schemas), but has other uses as well.

That probably doesn't make sense yet, so let's start with an example to show why dynamic references exist. This schema describes a recursive tree structure whose values are strings.

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

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

Here's an instance that validates against that schema.

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

Now, let's say we want to extend this schema so that each branch has at most two nodes. You might be tempted to try the following schema.

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

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

But, the maxItems constraint will only apply to the root of the tree. The recursive reference in /schemas/string-tree still points to itself, which doesn't constrain the number of nodes.

So, ["a", "b", "c"] would fail validation as expected because there are three nodes. But, ["a", ["b", "c", "d"]] would not fail because the branch with three nodes is validated against just the /schemas/string-tree schema, not the /schemas/bounded-string-tree schema.

So, in order to extend a recursive schema, I would need a way to allow an extending schema (/schemas/bound-string-tree) to change the target of references in the extended schema (/schemas/string-tree). Dynamic references provide that mechanism.

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

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

In this case, the dynamic reference and anchor work the same as a regular reference and anchor except that now the reference can be overridden by an extending schema if needed. If there is no extending schema, it will go to this dynamic anchor. If there is an extending schema that declares a matching dynamic anchor, it will override this one changing where the dynamic reference resolves to.

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

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

By setting the "branch" dynamic anchor in /schemas/bounded-string-tree, we are effectively overriding any future dynamic references to "branch" to resolve to this location.

Now, ["a", ["b", "c", "d"]] will fail validation against /schema/bounded-string-tree as expected.

You might have also heard of $recursiveRef in JSON Schema 2019-09. This was a previous incarnation of dynamic references that were only useful for extending recursive schemas like in this example. Unlike recursive references, dynamic references allow you to set more than one extension point in a schema. Let's take our example one step further to see why this is useful.

Let's say we want a schema that describes a tree, but we want extending schemas to be able to override the schema for the leaf of the tree. For example, we might want a tree with number leaf nodes instead of strings. We can use dynamic references to allow the leaf to be overridden.

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

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

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

Now we have two extension points and can create a bounded number tree.

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

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

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

There are a few more complexities to dynamic references that I won't go into for now. Hopefully this was enough to show why this complex mechanism exists and when you would want to use it. I hope it also made it seem a little less complicated.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1