Skip to content

Commit 48fb19b

Browse files
committed
Rework recursive reference section
This re-casts the behavior of "$recursiveAnchor" in terms of base URIs, and now this whole section is much easier to explain. The examples (or rather, an updated example) will be added in the next commit.
1 parent c5e8c62 commit 48fb19b

File tree

1 file changed

+46
-177
lines changed

1 file changed

+46
-177
lines changed

jsonschema-core.xml

Lines changed: 46 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -606,7 +606,7 @@
606606
due to the need to examine all subschemas for annotation collection, including
607607
those that cannot further change the assertion result.
608608
</t>
609-
<section title="Lexical Scope and Dynamic Scope">
609+
<section title="Lexical Scope and Dynamic Scope" anchor="scopes">
610610
<t>
611611
While most JSON Schema keywords can be evaluated on their own,
612612
or at most need to take into account the values or results of
@@ -1587,206 +1587,75 @@
15871587
</t>
15881588
</section>
15891589

1590-
<section title='Recursive References with "$recursiveRef" and "$recursiveAnchor"'>
1590+
<section title='Recursive References with "$recursiveRef" and "$recursiveAnchor"'
1591+
anchor="recursive-ref">
15911592
<t>
15921593
The "$recursiveRef" and "$recursiveAnchor" keywords are used to construct
15931594
extensible recursive schemas. A recursive schema is one that has
15941595
a reference to its own root, identified by the empty fragment
15951596
URI reference ("#").
15961597
</t>
1597-
<t>
1598-
Extending a recursive schema with "$ref" alone involves redefining all
1599-
recursive references in the source schema to point to the root of the
1600-
extension. This produces the correct recursive behavior in the extension,
1601-
which is that all recursion should reference the root of the extension.
1602-
</t>
1603-
<figure>
1604-
<preamble>
1605-
Consider the following two schemas. The first schema, identified
1606-
as "original" as it is the schema to be extended, describes
1607-
an object with one string property and one recursive reference
1608-
property, "r". The second schema, identified as "extension",
1609-
references the first, and describes an additional "things" property,
1610-
which is an array of recursive references.
1611-
It also repeats the description of "r" from the original schema.
1612-
</preamble>
1613-
<artwork>
1614-
<![CDATA[
1615-
{
1616-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1617-
"$id": "https://example.com/original",
1618-
1619-
"properties": {
1620-
"name": {
1621-
"type": "string"
1622-
},
1623-
"r": {
1624-
"$ref": "#"
1625-
}
1626-
}
1627-
}
1628-
1629-
{
1630-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1631-
"$id": "https://example.com/extension",
1632-
1633-
"$ref": "original",
1634-
"properties": {
1635-
"r": {
1636-
"$ref": "#"
1637-
},
1638-
"things": {
1639-
"type": "array"
1640-
"items": {
1641-
"$ref": "#"
1642-
}
1643-
}
1644-
}
1645-
}
1646-
]]>
1647-
</artwork>
1648-
<postamble>
1649-
This apparent duplication is important because
1650-
it resolves to "https://example.com/extension#", meaning that
1651-
for instance validated against the extension schema, the value
1652-
of "r" must be valid according to the extension, and not just the
1653-
original schema as "r" was described there.
1654-
</postamble>
1655-
</figure>
1656-
<t>
1657-
This approach is fine for a single recursive field, but the more
1658-
complicated the original schema, the more redefinitions are necessary
1659-
in the extension. This leads to a verbose and error-prone extension,
1660-
which must be kept synchronized with the original schema if the
1661-
original changes its recursive fields.
1662-
This approach can be seen in the meta-schema for JSON Hyper-Schema
1663-
in all prior drafts.
1664-
</t>
1665-
<section title='Enabling Recursion with "$recursiveAnchor"'>
1598+
<section title='Dynamically recursive references with "$recursiveRef"'>
1599+
<t>
1600+
The value of the "$recursiveRef" property MUST be a string which is
1601+
a URI-reference. It is a by-reference applicator that uses
1602+
a dynamically calculated base URI to resolve its value.
1603+
</t>
1604+
<t>
1605+
The behavior of this keyword is defined only for the value "#".
1606+
Implementations MAY choose to consider other values to be errors.
1607+
<cref>
1608+
This restriction may be relaxed in the future, but to date only
1609+
the value "#" has a clear use case.
1610+
</cref>
1611+
</t>
16661612
<t>
1667-
The desired behavior is for the recursive reference, "r", in the
1668-
original schema to resolve to the original schema when that
1669-
is the only schema being used, but to resolve to the extension
1670-
schema when using the extension. Then there would be no need
1671-
to redefine the "r" property, or others like it, in the extension.
1613+
The value of "$recursiveRef" is initially resolved against the
1614+
current base URI, in the same manner as for "$ref".
16721615
</t>
16731616
<t>
1674-
In order to create a recursive reference, we must do three things:
1675-
<list>
1676-
<t>
1677-
In our original schema, indicate that the schema author
1678-
intends for it to be extensible recursively.
1679-
</t>
1680-
<t>
1681-
In our extension schema, indicate that it is intended
1682-
to be a recursive extension.
1683-
</t>
1684-
<t>
1685-
Use a reference keyword that explicitly activates the
1686-
recursive behavior at the point of reference.
1687-
</t>
1688-
</list>
1689-
These three things together ensure that all schema authors
1690-
are intentionally constructing a recursive extension, which in
1691-
turn gives all uses of the regular "$ref" keyword confidence
1692-
that it only behaves as it appears to, using lexical scoping.
1617+
The schema identified by the resulting URI is examined for the
1618+
presence of "$recursiveAnchor", and a new base URI is calculated
1619+
as described for that keyword in the following section.
16931620
</t>
16941621
<t>
1695-
The "$recursiveAnchor" keyword is how schema authors indicate
1696-
that a schema can be extended recursively, and be a recursive
1697-
schema. This keyword MAY appear in the root schema of a
1698-
schema document, and MUST NOT appear in any subschema.
1622+
Finally, the value of "$recursiveRef" is resolved against the
1623+
new base URI determined according to "$recursiveAnchor" producing
1624+
the final resolved reference URI.
16991625
</t>
17001626
<t>
1701-
The value of "$recursiveAnchor" MUST be of type boolean, and
1702-
MUST be true. The value false is reserved for possible future use.
1627+
Note that in the absence of "$recursiveAnchor" (and in some cases
1628+
when it is present", "$recursiveRef"'s behavior is identical to
1629+
that of "$ref".
17031630
</t>
17041631
</section>
1705-
<section title='Dynamically recursive references with "$recursiveRef"'>
1632+
<section title='Enabling Recursion with "$recursiveAnchor"'>
17061633
<t>
1707-
The "$recursiveRef" keyword behaves identically to "$ref", except
1708-
that if the referenced schema has "$recursiveAnchor" set to true,
1709-
then the implementation MUST examine the dynamic scope for the
1710-
outermost (first seen) schema document with "$recursiveAnchor"
1711-
set to true. If such a schema document exists, then the target
1712-
of the "$recursiveRef" MUST be set to that document's URI, in
1713-
place of the URI produced by the rules for "$ref".
1634+
The value of the "$recursiveAnchor" property MUST be a boolean.
17141635
</t>
17151636
<t>
1716-
Note that if the schema referenced by "$recursiveRef" does not
1717-
contain "$recursiveAnchor" set to true, or if there are no other
1718-
"$recursiveAnchor" keywords set to true anywhere further back in
1719-
the dynamic scope, then "$recursiveRef"'s behavior is identical
1720-
to that of "$ref".
1637+
"$recursiveAnchor" is used to dynamically identify a base URI
1638+
at runtime for "$recursiveRef" by marking where such a calculation
1639+
can start, and where it stops. This keyword MUST NOT affect the
1640+
base URI of other keywords, unless they are explicitly defined
1641+
to rely on it.
1642+
</t>
1643+
<t>
1644+
If set to true, then when the containing schema object is used
1645+
as a dynamic reference target, a new base URI is determined
1646+
by examining the <xref target="scopes">dynamic scope</xref> for
1647+
the outermost schema that also contains "$recursiveAnchor"
1648+
with a value of true. The base URI of that schema is then used
1649+
as the dynamic base URI.
17211650
</t>
1722-
<figure>
1723-
<preamble>
1724-
With this in mind, we can rewrite the previous example:
1725-
</preamble>
1726-
<artwork>
1727-
<![CDATA[
1728-
{
1729-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1730-
"$id": "https://example.com/original",
1731-
"$recursiveAnchor": true,
1732-
1733-
"properties": {
1734-
"name": {
1735-
"type": "string"
1736-
},
1737-
"r": {
1738-
"$recursiveRef": "#"
1739-
}
1740-
}
1741-
}
1742-
1743-
{
1744-
"$schema": "https://json-schema.org/draft/2019-08/schema",
1745-
"$id": "https://example.com/extension",
1746-
"$recursiveAnchor": true,
1747-
1748-
"$ref": "original",
1749-
"properties": {
1750-
"things": {
1751-
"type": "array"
1752-
"items": {
1753-
"$recursiveRef": "#"
1754-
}
1755-
}
1756-
}
1757-
}
1758-
]]>
1759-
</artwork>
1760-
<postamble>
1761-
Note that the "r" property no longer appears in the
1762-
extension schema. Instead, all "$ref"s have been changed
1763-
to "$recursiveRef"s, and both schemas have "$recursiveAnchor"
1764-
set to true in their root schema.
1765-
</postamble>
1766-
</figure>
17671651
<t>
1768-
When using the original schema on its own, there is no change
1769-
in behavior. The "$recursiveRef" does lead to a schema where
1770-
"$recursiveAnchor" is set to true, but since the original schema
1771-
is the only schema document in the dynamics scope (it references
1772-
itself, and does not reference any other schema documents), the
1773-
behavior is effectively the same as "$ref".
1652+
If no such schema exists, then the base URI is unchanged.
17741653
</t>
17751654
<t>
1776-
When using the extension schema, the "$recursiveRef" within
1777-
that schema (for the array items within "things") also effectively
1778-
behaves like "$ref". The extension schema is the outermost
1779-
dynamic scope, so the reference target is not changed.
1655+
If this keyword is set to false, the base URI is unchanged.
17801656
</t>
17811657
<t>
1782-
In contrast, when using the extension schema, the "$recursiveRef"
1783-
for "r" in the original schema now behaves differently. Its
1784-
initial target is the root schema of the original schema document,
1785-
which has "$recursiveAnchor" set to true. In this case, the
1786-
outermost dynamic scope that also has "$recursiveAnchor" set to
1787-
true is the extension schema. So when using the extensions schema,
1788-
"r"'s reference in the original schema will resolve to
1789-
"https://example.com/extension#", not "https://example.com/original#".
1658+
Omitting this keyword has the same behavior as a value of false.
17901659
</t>
17911660
</section>
17921661
</section>

0 commit comments

Comments
 (0)