9

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.

Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
Andreas H.
  • 5,557
  • 23
  • 32

1 Answers1

25

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.

Jason Desrosiers
  • 22,479
  • 5
  • 47
  • 53
  • Nice answer, thanks. I have two questions about it, though: (1) in your `$defs` you labeled the sub-schema as `"leaf"` and called the anchor also `"leaf"`, do they have to match or could I have used "this_leaf_def" for the sub-schema? (2) Your `bounded-number-tree` has `maxItems: 2`, is this limitation for the number of _leaves_ or _branches_? Could you give an example for that, please? – Brandt Oct 06 '22 at 14:14
  • 2
    (1) The name of the definition isn't relevant. I could have called it "foo" and it would change anything. It doesn't even need to be in `$defs`. It could be any schema. (2) It limits the number of leaves. There is no way to limit the number of branches. – Jason Desrosiers Oct 06 '22 at 16:23
  • @Jason Desrosiers is there a way to run your tree example - the base schema and the extension schema - with your online Hyperjump JSON Schema validator? – Roger Costello Aug 02 '23 at 13:42
  • @RogerCostello Yes! The only thing missing from these schemas that you'll need is the `$schema` declaration for 2020-12. (You can copy it from the default schema on the site.) The site allows you to add multiple schemas, but the first tab is always the main entry point. So, to try the "bounded-number-tree" schema, put that schema in the first tab and the "base-tree" schema that it references in the second tab. – Jason Desrosiers Aug 03 '23 at 21:05
  • @Jason Desrosiers Ah! That works like a charm! Thank you! – Roger Costello Aug 04 '23 at 13:52