Repository navigation
Feature: Resolve $dynamicRef against $dynamicAnchor in the schema reference resolution pipeline #2911
Description
Activity
- marked Add support for dynamicRef/dynamicAnchor resolution #2912 as a duplicate of this issue
on Jun 26, 2026 - added a commit that references this issue
on Jun 28, 2026 aqeelat commented
on Jun 28, 2026 ContributorAuthorMore actionsUpdate: I opened #2913 implementing the first phase — document-scoped
$dynamicRefresolution viaOpenApiSchemaReference.Bare
$dynamicRefschemas (no$ref) now deserialize asOpenApiSchemaReferencewhoseTargetresolves through a per-document$dynamicAnchorindex inOpenApiWorkspace. Siblings are preserved viaApplySchemaMetadataand surfaced through the existingReference.X ?? Target?.Xproperty getters.The remaining design questions from this issue (full dynamic-scope resolution, inline schema anchor registration) are tracked as future work.
- added a commit that references this issue
on Jun 29, 2026 aqeelat commented
on Jun 29, 2026 ContributorAuthorMore actionsUpdated: #2913 now implements the complete design.
What is delivered:
- Bare
$dynamicRef→OpenApiSchemaReferencewithTargetresolution - Per-document
$dynamicAnchorand$anchorregistries, populated from the entire document tree (components, inline schemas, all nested subschema locations) $anchorfallback when zero$dynamicAnchorcandidates exist (per §8.2.3.2)- Two public APIs for multi-candidate resolution:
GetDynamicAnchorCandidatesandResolveDynamicAnchorInContext - Siblings preserved via
ApplySchemaMetadataand surfaced through existingReference.X ?? Target?.Xgetters
Known limitation: when multiple schemas declare the same
$dynamicAnchor,Targetreturns null because the library does not track evaluation context. The spec requires the outermost candidate in the dynamic scope — a deterministic answer, but one that needs to know which schema is the active entry point. Consumers provide this viaResolveDynamicAnchorInContext(entryPointSchema, anchorName). Whether the library should handle this automatically (e.g.,AsyncLocal<Stack>) is an open question raised in the PR.- Bare
- added 3 commits that reference this issue
on Jun 30, 2026 aqeelat commented
on Jul 3, 2026 ContributorAuthorMore actionsTracked as sub-task: #2928 (relative URI resolution in
$dynamicRef)- added 4 commits that reference this issue
on Jul 3, 2026
Background
JSON Schema 2020-12 defines
$dynamicRef/$dynamicAnchorfor recursive and extensible schema resources. This enables patterns where a base schema can recursively refer to the active derived schema:When
LocalizedCategoryis the active schema,children.itemsshould resolve toLocalizedCategory, notBaseCategory.Current State
After #2896, JSON Schema 2020-12 keywords such as
$dynamicRef,$dynamicAnchor, and$defsare parsed, preserved, and serialized, including as siblings on$refschemas.However,
$dynamicRefis not currently part of schema reference resolution:BaseOpenApiReferenceHolder.Targetresolves$refthroughOpenApiDocument.ResolveReference.OpenApiWorkspaceindexes schemas by component path and$id, but not by$dynamicAnchor.JsonNodeHelper.GetReferencePointeronly checks$ref.$dynamicRefbut no$refis deserialized as a plainOpenApiSchemawithDynamicRefset, so it does not enter theOpenApiSchemaReference.Targetresolution path.Design Questions
There are two separate decisions to make.
1. How should bare
$dynamicRefschemas participate in resolution?Options:
$dynamicRefsimilarly to$refduring schema loading, producing a schema reference type whose target resolution uses$dynamicAnchor.$dynamicRefas metadata onOpenApiSchema, but add a separate dynamic-ref resolution path outsideTarget.Option 1 integrates with the existing reference model but requires
Targetto resolve context-dependently. Option 2 keeps$dynamicRefresolution explicit and doesn't conflate it with static$refresolution.2. What scope should resolution support?
A document-level
$dynamicAnchorlookup handles the simple case where only one schema declares an anchor.Full JSON Schema 2020-12 behavior requires dynamic-scope resolution: when multiple resources declare the same
$dynamicAnchor, the outermost active schema resource wins. That requires context that the currentTarget/Workspace.ResolveReferencepipeline does not carry today.Targetsemantics$idregistration patternLoopDetector,ResolveRecursiveTarget$dynamicRefblocker?Related
$refsiblings ($defs,$dynamicAnchor,$id) are silently dropped when parsing #2895