Skip to content

feat(json-schema): resolve standalone JSON Schema 2020-12 documents - #4306

Closed
RobertHebel wants to merge 8 commits into
swagger-api:mainfrom
RobertHebel:feat/json-schema-2020-12
Closed

RobertHebel wants to merge 8 commits into
swagger-api:mainfrom
RobertHebel:feat/json-schema-2020-12

Conversation

@RobertHebel

@RobertHebel RobertHebel commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Draft: depends on unreleased ApiDOM (swagger-api/apidom#5279). Commit 108218ec installs @swagger-api/apidom-reference and @swagger-api/apidom-ns-json-schema-2020-12 from tarballs of swagger-api/apidom branch feat/json-schema-2020-12/phase-3 (11951862a). CI can't reach them. Before merging, drop that commit, bump every @swagger-api/apidom-* to the release and run every gate again.

What

  • New json-schema-2020-12-apidom resolve strategy for documents whose root $schema is https://json-schema.org/draft/2020-12/schema (an empty fragment is normalised) and whose root has none of openapi, swagger, asyncapi and arazzo. Any other document keeps the strategy it had (generic for AsyncAPI/Arazzo, refused $schema values and nested-only $schema).
  • $refs resolve over apidom-reference's json-schema-2020-12 strategies:
    • against $id base URIs
    • to $anchor and $dynamicAnchor plain names
    • to external documents, as in OpenAPI 3.1 Schema Objects
  • Every $dynamicRef is left in place.
  • A $ref from a resolved subtree to one of its ancestors ("$ref": "#") is a cycle, so resolveSubtree, Swagger UI's path, works on recursive schemas.
  • The dereference strategy merges allOf (unless mode: 'strict') and applies modelPropertyMacro, so a schema resolves the same standalone and inside an OpenAPI 3.1 document.
  • The JSON/YAML parsers detect a document by its parsed root and are used only by this strategy. The OpenAPI parser lists are unchanged, and a regression test covers OpenAPI 3.1 external targets with a root 2020-12 $schema.
  • Registered before generic in Swagger.resolve and Swagger.resolveSubtree. The parsers and the dereference strategy are exported in Swagger.apidom.
  • ApiDOM is bumped to 1.13.3 in its own commit. The README, CLAUDE.md and installation docs are updated.

Out of scope: references that cross dialects; bundling; inferring the dialect without $schema. The same ancestor-cycle defect in the OpenAPI 3.1/3.2 strategies is fixed separately in #4304.

Known limitations (apidom-reference, same as OpenAPI 3.1; each pinned by a test.failing fixture):

  • the uri selector misses a relative $id nested in another relative $id (id-relative-nested);
  • the $anchor selector also searches embedded $id resources and takes the first match, so a reused anchor name can select the embedded resource's schema (anchor-embedded-resource).

For Swagger UI: import swagger-client/es/resolver/strategies/json-schema-2020-12-apidom into both strategy lists.

🤖 Generated with Claude Code

RobertHebel and others added 8 commits October 26, 2026 23:54
Move every @swagger-api/apidom-* package to 1.13.3, add
@swagger-api/apidom-ns-json-schema-2020-12 to dependencies (the JSON Schema
2020-12 resolve strategy imports it) and its two parser adapters to
optionalDependencies, mirroring apidom-reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erge)

Install @swagger-api/apidom-reference and
@swagger-api/apidom-ns-json-schema-2020-12 from tarballs packed from the
ApiDOM branch feat/json-schema-2020-12/phase-3 at commit 11951862a, which
adds the json-schema-2020-12 resolve and dereference strategies and exports
keyMap and getNodeType from the namespace. The tarballs live in
../upstream-tarballs; "$" overrides make nested copies use them.

Drop this commit once ApiDOM releases the branch, and bump every
@swagger-api/apidom-* package to that release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the json-schema-2020-12-apidom resolve strategy for documents whose root
$schema is https://json-schema.org/draft/2020-12/schema (an empty fragment
normalised) and which have no openapi or swagger field. Any other $schema
keeps the generic strategy. The strategy resolves over apidom-reference's
json-schema-2020-12 strategies, so $refs resolve against $id base URIs,
$anchor and $dynamicAnchor plain names and external documents as in
OpenAPI 3.1 Schema Objects; every $dynamicRef is left in place.

The json-schema-2020-12-swagger-client dereference strategy carries over the
OpenAPI 3.1 Schema Object handling (cycles, $$ref meta patches, collected
errors), merges allOf unless mode is strict and applies modelPropertyMacro,
so a schema resolves the same standalone and inside an OpenAPI 3.1
document. A $ref in the document root keeps its replacement as the parse
result. The JSON and YAML parsers detect a document by its parsed root and
are used only by this strategy; the OpenAPI parser lists are unchanged.

Register the strategy ahead of generic in Swagger.resolve and
Swagger.resolveSubtree, and export the parsers and the dereference strategy
in Swagger.apidom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record the decisions the 2020-12 work answered (cross-dialect references,
unreleased upstream, content-detecting parsers, $ref to $dynamicAnchor) in
design-and-delivery.md, and in strategy-design.md refract with the
namespace's refract (apidom-ns-arazzo-1 reassigns JSONSchemaElement.refract)
and keep the result class on a dereferenced root.

FOLLOW-UPS: keyMap/getNodeType are exported on the unreleased ApiDOM
branch; new item for apidom-reference's uri selector, which resolves
candidate $id chains against the $ref target instead of the document's
base URI (OpenAPI 3.1 and 3.2 alike).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dereference lineage held only the pathDiscriminator fragment, so a $ref
from the fragment to one of its ancestors was not a cycle: "$ref": "#" in a
recursive schema overflowed the stack when Swagger UI resolved
['properties'] or ['properties', 'children'] through resolveSubtree, and
['properties', 'children', 'items'] returned null. Seed the lineage with
the document root and every element along the pathDiscriminator, so such a
$ref resolves to an absolute $ref, as cycles do everywhere else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A document is standalone JSON Schema only when its root has the 2020-12
$schema and none of the API description fields openapi, swagger, asyncapi
and arazzo. AsyncAPI and Arazzo documents may declare a root $schema; they
keep the generic strategy they had, which regression tests check by
comparing with a generic-only resolve. The parsers detect through the same
predicate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
apidom-reference's $anchor selector searches embedded $id resources too and
takes the first match, so "#tag" can select the embedded resource's schema
instead of the referencing resource's (JSON Schema Core 8.2.2). OpenAPI 3.1
Schema Objects behave the same. Pin it with a test.failing fixture that
starts failing once ApiDOM stops the search at nested $ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
State the matching rule as no openapi, swagger, asyncapi or arazzo field
(D18) wherever it appears, record D18 and D19 in design-and-delivery.md,
require the pathDiscriminator lineage in strategy-design.md, and in
FOLLOW-UPS put 2.3 after 2.2 and add 2.4 for apidom-reference's $anchor
selector, which searches embedded schema resources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@robert-hebel-sb

Copy link
Copy Markdown
Contributor

Superseded: recreated with commits cherry-picked onto latest main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants