Skip to content

Commit 3b538ff

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 41cb6a3 commit 3b538ff

File tree

1 file changed

+222
-23
lines changed

1 file changed

+222
-23
lines changed

jsonschema-core.xml

Lines changed: 222 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,35 +1006,234 @@
10061006
</section>
10071007
</section>
10081008

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

0 commit comments

Comments
 (0)