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

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions