Skip to content

Commit 27f96c3

Browse files
committed
$recursiveRef and $recursiveAnchor
These keywords allow for recursive extension of schemas, in the way needed by meta-schemas. This is not Object-Oriented extension, rather it is a shortcut for the kind of "allOf" combination that is already allowed, but without the need to redeclare all recursive keywords in the extension. It works by both the original target of the reference and the dynamically chosen target of the reference opting in to the dynamic behavior with "$recursiveAnchor", and the source of the reference opting in by using "$recursiveRef". This ensures that regular "$ref" usage is unchanged and and predictable, and also that schemas not intended for recursive extension cannot be extended without being changed.
1 parent 0cce8aa commit 27f96c3

File tree

1 file changed

+222
-23
lines changed

1 file changed

+222
-23
lines changed

jsonschema-core.xml

+222-23
Original file line numberDiff line numberDiff line change
@@ -953,35 +953,234 @@
953953
</section>
954954
</section>
955955

956-
<section title='Schema References With "$ref"' anchor="ref">
956+
<section title="Schema References">
957957
<t>
958-
The "$ref" keyword can be used to reference a schema which is to be applied to the
959-
current instance location. "$ref" is an applicator key word, applying the referred
960-
schema to the instance.
958+
Several keywords can be used to reference a schema which is to be applied to the
959+
current instance location. "$ref" and "$recursiveRef" are an applicator
960+
keywords, applying the referred schema to the instance. "$recursiveAnchor"
961+
is a helper keyword that controls how the referred schema of "$recursiveRef"
962+
is determined.
961963
</t>
962964
<t>
963-
The value of the "$ref" property MUST be a string which is a URI Reference.
964-
Resolved against the current URI base, it identifies the URI of a schema to use.
965+
As the value of "$ref" and "$recursiveRef" are URI References, this allows
966+
the possibility to externalise or divide a schema across multiple files,
967+
and provides the ability to validate recursive structures through
968+
self-reference.
965969
</t>
966970
<t>
967-
As the value of "$ref" is a URI Reference, this allows the possibility to externalise or
968-
divide a schema across multiple files, and provides the ability to validate recursive structures
969-
through self-reference.
970-
</t>
971-
<t>
972-
The URI is not a network locator, only an identifier. A schema need not be
973-
downloadable from the address if it is a network-addressable URL, and
974-
implementations SHOULD NOT assume they should perform a network operation when they
975-
encounter a network-addressable URI.
976-
</t>
977-
<t>
978-
A schema MUST NOT be run into an infinite loop against a schema. For example, if two
979-
schemas "#alice" and "#bob" both have an "allOf" property that refers to the other,
980-
a naive validator might get stuck in an infinite recursive loop trying to validate
981-
the instance.
982-
Schemas SHOULD NOT make use of infinite recursive nesting like this; the behavior is
983-
undefined.
971+
The resolved URI produced by these keywords is not necessarily a network
972+
locator, only an identifier. A schema need not be downloadable from the
973+
address if it is a network-addressable URL, and implementations SHOULD NOT
974+
assume they should perform a network operation when they encounter
975+
a network-addressable URI.
984976
</t>
977+
978+
<section title='Direct References with "$ref"' anchor="ref">
979+
<t>
980+
The "$ref" keyword is used to reference a statically identified schema.
981+
</t>
982+
<t>
983+
The value of the "$ref" property MUST be a string which is a URI Reference.
984+
Resolved against the current URI base, it identifies the URI of a schema
985+
to use.
986+
</t>
987+
</section>
988+
989+
<section title='Recursive References with "$recursiveRefe" and "$recursiveAnchor"'>
990+
<t>
991+
The "$recursiveRef" and "$recursiveAnchor" keywords are used to construct
992+
extensible recursive schemas.
993+
</t>
994+
<t>
995+
Intuitively, when using "$ref" or another
996+
similar keyword to combine a recursive schema with another schema (recursive
997+
or otherwise), the goal of the schema author is often to have the
998+
recursion respect that combination. The recursive reference would
999+
ideally always recurse to where the processing of the schema started.
1000+
</t>
1001+
<t>
1002+
But this is not possible with the static behavior of "$ref", wich can
1003+
only refer to the root schema of the current schema document.
1004+
More accurately, it can only refer to one location, and that location
1005+
is constrained by the static rules for resolving URI References.
1006+
</t>
1007+
<t>
1008+
This constraint leads to verbose and error-prone re-definitions of each
1009+
recursive element, as can be seen in the meta-scheme for JSON Hyper-Schema
1010+
in all prior drafts.
1011+
</t>
1012+
<figure>
1013+
<preamble>
1014+
Consider the following two schemas. The first (given the id "basic")
1015+
is an object with one string property and one reference property.
1016+
The reference is recursive, pointing to the root of the current
1017+
schema document. The second schema references the first, and
1018+
also describes a "things" property, which is an array of
1019+
recursive references.
1020+
</preamble>
1021+
<artwork>
1022+
<![CDATA[
1023+
{
1024+
"$schema": "http://json-schema.org/draft-08/schema#",
1025+
"$id": "https://example.com/basic",
1026+
"$comment": "$ref: # referrs here from in this 'basic' file",
1027+
"properties": {
1028+
"name": {
1029+
"type": "string"
1030+
},
1031+
"recursive": {
1032+
"$ref": "#"
1033+
}
1034+
}
1035+
}
1036+
1037+
{
1038+
"$schema": "http://json-schema.org/draft-08/schema#",
1039+
"$id": "https://example.com/extension",
1040+
"$comment": "$ref: # referrs here from in this 'extension' file",
1041+
"$ref": "basic",
1042+
"properties": {
1043+
"things": {
1044+
"type": "array"
1045+
"items": {
1046+
"$ref": "#"
1047+
}
1048+
}
1049+
}
1050+
}
1051+
]]>
1052+
</artwork>
1053+
<postamble>
1054+
The problem is that the referred targets of the
1055+
<spanx style="verb">"$ref": "#"</spanx>
1056+
references are statically determined. Since the
1057+
<spanx style="verb">"things"</spanx> array is in the
1058+
combined schema, its referred schema is the combined
1059+
schema. But the <spanx style="verb">"recursive"</spanx>
1060+
property in the basic schema still points to the root
1061+
of that basic schema, and therefore will not see the
1062+
description of the <spanx style="verb">"things"</spanx>
1063+
property. What we want is for it to resolve
1064+
to the combined schema as well.
1065+
</postamble>
1066+
</figure>
1067+
<section title='Enabling Recursion with "$recursiveAnchor"'>
1068+
<t>
1069+
Since the desired behavior can seem surprising, and unpredictable,
1070+
it is important to use keywords to explicitly control all aspects
1071+
of the behavior. In order to create a recursive reference, we
1072+
must do three things:
1073+
<list>
1074+
<t>
1075+
In our "basic" schema, indicate that the schema author
1076+
intends for it to be extensible recursively.
1077+
</t>
1078+
<t>
1079+
In our "extension" schema, indicate that it is intended
1080+
to be a recursive extension.
1081+
</t>
1082+
<t>
1083+
Use a reference keyword that explicitly activates the
1084+
recursive behavior at the point of reference.
1085+
</t>
1086+
</list>
1087+
These three things together ensure that all schema authors
1088+
are intentionally constructing a recursive extension, which in
1089+
turn gives all uses of the regular "$ref" keyword confidence
1090+
that it only behaves as it appears to, using lexical scoping.
1091+
</t>
1092+
<t>
1093+
The "$recursiveAnchor" keyword is how schema authors indicate
1094+
that a schema can be extended recursively, and be a recursive
1095+
schema. This keyword MAY appear in the root schema of a
1096+
schema document, and MUST NOT appear in a subschema.
1097+
</t>
1098+
<t>
1099+
The value of "$recursiveAnchor" MUST be of type boolean, and
1100+
MUST be true. The value false is reserved for possible future use.
1101+
</t>
1102+
<t>
1103+
The "$recursiveRef" keyword behaves identically to "$ref", except
1104+
that if the referred schema has "$recursiveAnchor" set to true,
1105+
then the implementation MUST check the dyanamic scope to see
1106+
if "$recursiveAnchor" had previously been set. If so, then the
1107+
referred schema is considered to be the outermost (in terms of
1108+
dynamic scope) schema object containing "$recursiveAnchor" set to true.
1109+
</t>
1110+
<t>
1111+
Note that if the schema to which "$recursiveRef" referrs does not
1112+
contain "$recursiveAnchor" set to true, or if there are no other
1113+
"$recursiveAnchor" keywords set to true anywhere further back in
1114+
the dynamic scope, then "$recursiveRef"'s behavior is identical
1115+
to that of "$ref".
1116+
</t>
1117+
<figure>
1118+
<preamble>
1119+
With this in mind, we can rewrite the previous example:
1120+
</preamble>
1121+
<artwork>
1122+
<![CDATA[
1123+
{
1124+
"$schema": "http://json-schema.org/draft-08/schema#",
1125+
"$id": "https://example.com/basic",
1126+
"$recursiveAnchor": true,
1127+
1128+
"properties": {
1129+
"name": {
1130+
"type": "string"
1131+
},
1132+
"recursive": {
1133+
"$recursiveRef": "#"
1134+
}
1135+
}
1136+
}
1137+
1138+
{
1139+
"$schema": "http://json-schema.org/draft-08/schema#",
1140+
"$id": "https://example.com/extension",
1141+
"$recursiveAnchor": true,
1142+
1143+
"$ref": "basic",
1144+
"properties": {
1145+
"things": {
1146+
"type": "array"
1147+
"items": {
1148+
"$recursiveRef": "#"
1149+
}
1150+
}
1151+
}
1152+
}
1153+
]]>
1154+
</artwork>
1155+
<postamble>
1156+
Now lets consider the evaluation of the "extension" schema.
1157+
Note that the "$ref": "basic" was not changed, as it works
1158+
just fine as a normals static reference. And the
1159+
"$recursiveRef" in the "extended" schema does not behave at
1160+
all differently, because the "$recursiveAnchor" in its
1161+
referred schema is the outermost "$recursiveAnchor" in the
1162+
dynamic scope. However, the "$recursiveRef" in the "basic"
1163+
schema referrs to a "$recursiveAnchor" that is not the
1164+
outermost such keyword in the dynamic scope. That is still
1165+
the "$recursiveAnchor" in the "extension" schema.
1166+
Therefore, when processing starts with the extension
1167+
schema, the "$recursiveRef" in the basic schema actually
1168+
referrs to the "extension" schema's root schema.
1169+
</postamble>
1170+
</figure>
1171+
</section>
1172+
</section>
1173+
1174+
<section title="Guarding Against Inifinite Recursion">
1175+
<t>
1176+
A schema MUST NOT be run into an infinite loop against an instance. For
1177+
example, if two schemas "#alice" and "#bob" both have an "allOf" property
1178+
that refers to the other, a naive validator might get stuck in an infinite
1179+
recursive loop trying to validate the instance. Schemas SHOULD NOT make
1180+
use of infinite recursive nesting like this; the behavior is undefined.
1181+
</t>
1182+
</section>
1183+
9851184
<section title="Loading a referenced schema">
9861185
<t>
9871186
The use of URIs to identify remote schemas does not necessarily mean anything is downloaded,

0 commit comments

Comments
 (0)