Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 28 additions & 53 deletions specs/jsonschema-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -1022,23 +1022,16 @@ just a fragment identifier not an IRI reference. To reference the "foo"
`$anchor` from the same schema resource, you would use the fragment-only IRI
`#foo`. See below for full examples.

`$dynamicAnchor` defines a different kind of fragment identifier that only has
meaning when used with `$dynamicRef`. It's not a normal fragment identifier and
therefore can't be used anywhere other than `$dynamicRef`. Normal [fragment
identifiers](https://www.rfc-editor.org/rfc/rfc3986#section-3.5) identify the
secondary resource (the subschema) while the rest of the IRI identifies the
primary resource (the schema resource). The fragment identifiers defined by
`$dynamicAnchor` are not normal fragment identifies because they identify both
the primary resource and the secondary resource. See {{dynamic-ref}} for
details.
`$dynamicAnchor` defines a (non-fragment) identifier that only has meaning when
used with `$dynamicRef`.

If present, the value of these keywords MUST be a string and MUST conform to the
plain name fragment identifier syntax defined in {{fragments}}.

`$anchor`, `$dynamicAnchor`, and any extensions that define a plain name
fragment identifiers MUST match XML's [`NCName`
production](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#NT-NCName). For
convenience, the `NCName` syntax is reproduced here in ABNF form, using a
`$anchor`, `$dynamicAnchor`, and any extensions that define plain name
fragment identifiers MUST match XML's
[`NCName` production](https://www.w3.org/TR/2006/REC-xml-names11-20060816/#NT-NCName).
For convenience, the `NCName` syntax is reproduced here in ABNF form, using a
minimal set of rules:

```abnf
Expand All @@ -1058,9 +1051,9 @@ NCNameChar = NCNameStartChar / "-" / "." / DIGIT

A schema MAY (and likely will) have multiple IRIs, but there is no way for an
IRI to identify more than one schema. When multiple schemas attempt to identify
as the same IRI through the use of `$id`, `$anchor`, `$dynamicAnchor`, or any
other mechanism, implementations SHOULD raise an error condition. Otherwise the
result is undefined, and even if documented will not be interoperable.
as the same IRI through the use of `$id`, `$anchor`, or any other mechanism,
implementations SHOULD raise an error condition. Otherwise the result is
undefined, and even if documented will not be interoperable.

#### Schema References {#references}

Expand Down Expand Up @@ -1094,12 +1087,10 @@ resolve. This is useful for cases such as authoring a recursive schema that can
be extended or a generic schema such as a list whose items are defined by the
referencing schema.

The value of the `$dynamicRef` property MUST be formatted as a valid
[fragment-only IRI](#fragments).[^3]
The value of the `$dynamicRef` property MUST be a string and MUST conform to the
plain name fragment identifier syntax defined in {{fragments}}.[^3]

[^3]: `$dynamicAnchor` defines the anchor with plain text, e.g. `foo`. Although,
for historical reasons, the value of `$dynamicRef` still uses a fragment-only
IRI syntax, e.g. `#foo`.
[^3]: `$dynamicAnchor` and `$dynamicRef` form a string-matched pair.

Resolution of `$dynamicRef` begins by identifying the outermost schema resource
in the [dynamic scope](#scopes) which defines a matching `$dynamicAnchor`. The
Expand Down Expand Up @@ -2323,7 +2314,7 @@ and only allows the "data" and "children" properties. An example instance with
"children": {
"type": "array",
"items": {
"$dynamicRef": "#node"
"$dynamicRef": "node"
}
}
}
Expand All @@ -2348,50 +2339,34 @@ and only allows the "data" and "children" properties. An example instance with
```

When we load these two schemas, we will notice the `$dynamicAnchor` named "node"
(note the lack of "#" as this is just the name) present in each, resulting in
the following full schema IRIs:

- `https://example.com/tree#node`
- `https://example.com/strict-tree#node`

In addition, JSON Schema implementations keep track of the fact that these
fragment identifiers were created with `$dynamicAnchor`.
present in each.

If we apply the "strict-tree" schema to the instance, we will follow the `$ref`
to the "tree" schema, examine its "children" subschema, and find the
`$dynamicRef`: to "#node" (note the `#` for IRI fragment syntax) in its `items`
subschema. That reference resolves to `https://example.com/tree#node`, which is
a IRI with a fragment created by `$dynamicAnchor`. Therefore we must examine the
dynamic scope before following the reference.
`$dynamicRef` to "node" in its `items` subschema. That reference resolves to
the `$dynamicAnchor` with value "node" in `https://example.com/tree`. Therefore
we must examine the dynamic scope before following the reference.

At this point, the evaluation path is
`/$ref/properties/children/items/$dynamicRef`, with a dynamic scope containing
(from the outermost scope to the innermost):

1. `https://example.com/strict-tree#`
1. `https://example.com/tree#`
1. `https://example.com/tree#/properties/children`
1. `https://example.com/tree#/properties/children/items`

Since we are looking for a plain name fragment identifier, which can be defined
anywhere within a schema resource, the JSON Pointer IRI fragments are irrelevant
to this check. That means that we can remove the fragments and eliminate
consecutive duplicates, producing:

1. `https://example.com/strict-tree`
1. `https://example.com/tree`
2. `https://example.com/tree`

In this case, the outermost resource also has a "node" fragment identifier
defined by `$dynamicAnchor`. Therefore instead of resolving the `$dynamicRef` to
`https://example.com/tree#node`, we resolve it to
`https://example.com/strict-tree#node`.
To find the resolution target of the `$dynamicRef`, we start at the outermost
dynamic scope and traverse inward, stopping when we find a schema resource that
defines a matching `$dynamicAnchor`.

The reference in the "tree" schema resolves to the root of "strict-tree", so
"strict-tree" is applied not only to the tree instance's root, but also its
children.
In this case, the outermost resource "strict-tree" has a "node" identifier
defined by `$dynamicAnchor`. The subschema that defines that anchor is the
resolution target of the `$dynamicRef`. Therefore instead of resolving the
`$dynamicRef` to the `"$dynamicAnchor": "node"` in `https://example.com/tree`,
we resolve it to the `"$dynamicAnchor": "node"` in
`https://example.com/strict-tree`.

This example shows both `$dynamicAnchor`s in the same place in each schema,
specifically the resource root schema. Since plain-name fragment identifiers are
specifically the resource root schema. Since plain-name identifiers are
independent of the JSON structure, this would work just as well if one or both
of the node schema objects were moved under `$defs`. It is the matching
`$dynamicAnchor` values which tell us how to resolve the dynamic reference, not
Expand Down
4 changes: 2 additions & 2 deletions specs/meta/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ First, create a schema that just defines the new keywords.
"$id": "https://example.com/schema/odds-evens",

"properties": {
"odds": { "$dynamicRef": "#meta" },
"evens": { "$dynamicRef": "#meta" }
"odds": { "$dynamicRef": "meta" },
"evens": { "$dynamicRef": "meta" }
}
}
```
Expand Down
36 changes: 18 additions & 18 deletions specs/meta/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"$schema": { "$ref": "#/$defs/iriString" },
"$ref": { "$ref": "#/$defs/iriReferenceString" },
"$anchor": { "$ref": "#/$defs/anchorString" },
"$dynamicRef": { "$ref": "#/$defs/iriReferenceString" },
"$dynamicRef": { "$ref": "#/$defs/anchorString" },
"$dynamicAnchor": { "$ref": "#/$defs/anchorString" },
"$comment": {
"type": "string"
},
"$defs": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" }
"additionalProperties": { "$dynamicRef": "meta" }
},
"title": {
"type": "string"
Expand All @@ -47,40 +47,40 @@
"items": true
},
"prefixItems": { "$ref": "#/$defs/schemaArray" },
"items": { "$dynamicRef": "#meta" },
"items": { "$dynamicRef": "meta" },
"maxContains": { "$ref": "#/$defs/nonNegativeInteger" },
"minContains": {
"$ref": "#/$defs/nonNegativeInteger",
"default": 1
},
"contains": { "$dynamicRef": "#meta" },
"additionalProperties": { "$dynamicRef": "#meta" },
"contains": { "$dynamicRef": "meta" },
"additionalProperties": { "$dynamicRef": "meta" },
"properties": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"additionalProperties": { "$dynamicRef": "meta" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"additionalProperties": { "$dynamicRef": "meta" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependentSchemas": {
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"additionalProperties": { "$dynamicRef": "meta" },
"default": {}
},
"propertyNames": { "$dynamicRef": "#meta" },
"if": { "$dynamicRef": "#meta" },
"then": { "$dynamicRef": "#meta" },
"else": { "$dynamicRef": "#meta" },
"propertyNames": { "$dynamicRef": "meta" },
"if": { "$dynamicRef": "meta" },
"then": { "$dynamicRef": "meta" },
"else": { "$dynamicRef": "meta" },
"allOf": { "$ref": "#/$defs/schemaArray" },
"anyOf": { "$ref": "#/$defs/schemaArray" },
"oneOf": { "$ref": "#/$defs/schemaArray" },
"not": { "$dynamicRef": "#meta" },
"unevaluatedItems": { "$dynamicRef": "#meta" },
"unevaluatedProperties": { "$dynamicRef": "#meta" },
"not": { "$dynamicRef": "meta" },
"unevaluatedItems": { "$dynamicRef": "meta" },
"unevaluatedProperties": { "$dynamicRef": "meta" },
"type": {
"anyOf": [
{ "$ref": "#/$defs/simpleTypes" },
Expand Down Expand Up @@ -137,7 +137,7 @@
"format": { "type": "string" },
"contentEncoding": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentSchema": { "$dynamicRef": "#meta" },
"contentSchema": { "$dynamicRef": "meta" },

"$vocabulary": {
"$comment": "Proposed keyword: https://github.com/json-schema-org/json-schema-spec/blob/main/specs/proposals/vocabularies.md"
Expand All @@ -152,7 +152,7 @@
"propertyNames": {
"pattern": "^[^$]|^\\$(id|schema|ref|anchor|dynamicRef|dynamicAnchor|comment|defs)$"
},
"$dynamicRef": "#extension",
"$dynamicRef": "extension",
"unevaluatedProperties": false,
"$defs": {
"extension": {
Expand Down Expand Up @@ -181,7 +181,7 @@
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$dynamicRef": "#meta" }
"items": { "$dynamicRef": "meta" }
},
"simpleTypes": {
"enum": [
Expand Down
2 changes: 1 addition & 1 deletion specs/proposals/propertyDependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ vocabulary](../jsonschema-core.md#applicators).
"additionalProperties": {
"type": "object",
"additionalProperties": {
"$dynamicRef": "#meta",
"$dynamicRef": "meta",
"default": true
},
"default": {}
Expand Down
4 changes: 2 additions & 2 deletions specs/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"definitions": {
"$comment": "\"definitions\" has been replaced by \"$defs\".",
"type": "object",
"additionalProperties": { "$dynamicRef": "#meta" },
"additionalProperties": { "$dynamicRef": "meta" },
"deprecated": true,
"default": {}
},
Expand All @@ -37,7 +37,7 @@
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$dynamicRef": "#meta" },
{ "$dynamicRef": "meta" },
{ "$ref": "meta/validation#/$defs/stringArray" }
]
},
Expand Down
Loading