Skip to content

Feature: Resolve $dynamicRef against $dynamicAnchor in the schema reference resolution pipeline #2911

Description

@aqeelat

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.Target resolves $ref through OpenApiDocument.ResolveReference.
  • OpenApiWorkspace indexes schemas by component path and $id, but not by $dynamicAnchor.
  • JsonNodeHelper.GetReferencePointer only checks $ref.
  • A schema with $dynamicRef but no $ref is deserialized as a plain OpenApiSchema with DynamicRef set, so it does not enter the OpenApiSchemaReference.Target resolution path.

Design Questions

There are two separate decisions to make.

1. How should bare $dynamicRef schemas participate in resolution?

Options:

  1. Treat $dynamicRef similarly to $ref during schema loading, producing a schema reference type whose target resolution uses $dynamicAnchor.
  2. Keep $dynamicRef as metadata on OpenApiSchema, but add a separate dynamic-ref resolution path outside Target.

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

Metadata

Metadata

Assignees

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions