Repository navigation
Feature: Resolve $dynamicRef against $dynamicAnchor in the schema reference resolution pipeline #2911
Description
Background
JSON Schema 2020-12 defines $dynamicRef / $dynamicAnchor for recursive and extensible schema resources. This enables patterns where a base schema can recursively refer to the active derived schema:
components:
schemas:
BaseCategory:
$dynamicAnchor: category
type: object
properties:
children:
type: array
items:
$dynamicRef: '#category'
LocalizedCategory:
$dynamicAnchor: category
allOf:
- $ref: '#/components/schemas/BaseCategory'When LocalizedCategory is the active schema, children.items should resolve to LocalizedCategory, not BaseCategory.
Current State
After #2896, JSON Schema 2020-12 keywords such as $dynamicRef, $dynamicAnchor, and $defs are parsed, preserved, and serialized, including as siblings on $ref schemas.
However, $dynamicRef is 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.- A schema with
$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 $dynamicRef schemas participate in resolution?
Options:
- Treat
$dynamicRefsimilarly to$refduring schema loading, producing a schema reference type whose target resolution uses$dynamicAnchor. - Keep
$dynamicRefas metadata onOpenApiSchema, but add a separate dynamic-ref resolution path outsideTarget.
Option 1 integrates with the existing reference model but requires Target to resolve context-dependently. Option 2 keeps $dynamicRef resolution explicit and doesn't conflate it with static $ref resolution.
2. What scope should resolution support?
A document-level $dynamicAnchor lookup 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 current Target / Workspace.ResolveReference pipeline does not carry today.
| Aspect | Document-scoped lookup | Dynamic-scope resolution |
|---|---|---|
| Simple recursion (single anchor) | Works | Works |
| Generic types (multiple anchors, same name) | Fails — no context | Works |
| Implementation size | Small (index + lookup) | Large (scope plumbing + walker changes) |
Risk to existing Target semantics |
Low (additive) | Medium-high (new resolution path) |
| Precedent in repo | $id registration pattern |
LoopDetector, ResolveRecursiveTarget |
| Order-dependency | None | Inherent (documented in kiota) |
Solves the pure-$dynamicRef blocker? |
Needs deserializer change | Needs deserializer change |
Related
- fix: preserve JSON Schema 2020-12 keyword siblings on $ref schemas for OAS 3.1+ #2896
- Bug: OpenAPI 3.1
$refsiblings ($defs,$dynamicAnchor,$id) are silently dropped when parsing #2895 - Add missing keywords support for OpenAPIReference (and JsonSchemaRefernece #2903
- Feature: Support $dynamicRef / $dynamicAnchor (JSON Schema 2020-12) for OpenAPI 3.1+ kiota#7815
- feat: resolve $dynamicRef against $dynamicAnchor for recursive types kiota#7817
Activity
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
aqeelat commentedon Jun 28, 2026
Update: 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.
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
aqeelat commentedon Jun 29, 2026
Updated: #2913 now implements the complete design.
What is delivered:
$dynamicRef→OpenApiSchemaReferencewithTargetresolution$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)GetDynamicAnchorCandidatesandResolveDynamicAnchorInContextApplySchemaMetadataand surfaced through existingReference.X ?? Target?.XgettersKnown 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.feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
aqeelat commentedon Jul 3, 2026
Tracked as sub-task: #2928 (relative URI resolution in
$dynamicRef)feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…
feat(schema): resolve $dynamicRef against $dynamicAnchor via OpenApiS…