You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
$dynamicRef / $dynamicAnchor Support for OpenAPI 3.1+
Summary
Kiota silently degrades OpenAPI 3.1+ schemas that use JSON Schema 2020-12 $dynamicRef / $dynamicAnchor (spec §7.7) to UntypedNode, which becomes object / any / unknown across all output languages. No error, no warning. The upstream parser (Microsoft.OpenApi 3.7.0) already parses and exposes both keywords on IOpenApiSchema, but Kiota's code-generation pipeline never reads them.
This affects two real-world uses:
Recursive self-referential types (validator-backed) — e.g. a category tree where LocalizedCategory.children should be LocalizedCategory[] but generates as unknown[].
Generic / reusable response wrappers (mixed validator support) — e.g. PaginatedTemplate<T> with PaginatedUserResponse = PaginatedTemplate<User> and PaginatedGroupResponse = PaginatedTemplate<Group>. Kiota currently can't emit the reusable template; consumers get duplicated concrete wrappers or untyped items.
Reproduction
Minimal fixtures (small, single-purpose) are available in the tracker repo:
Running any of these through kiota generate -d <fixture> -l typescript reproduces the issue.
Root cause (verified against main HEAD 3939d526d)
Zero references to DynamicRef / DynamicAnchor anywhere in the Kiota tree (source + tests).
KiotaBuilder.CreateModelDeclarations (src/Kiota.Builder/KiotaBuilder.cs:1900) dispatches on IsReferencedSchema() (which only matches OpenApiSchemaReference, i.e. $ref), inheritance, intersection, union, object, array, primitive — a schema whose only content is { $dynamicRef: '#foo' } matches none of these and falls through to the UntypedNode fallback at KiotaBuilder.cs:1977.
Microsoft.OpenApi 3.7.0 exposes IOpenApiSchema.DynamicRef / IOpenApiSchema.DynamicAnchor, and OpenApiSchemaReference delegates both from its Target. Phase 1 is unblocked on this front — Kiota just needs to read these properties. Phase 2 has a deeper upstream issue: the 3.1 deserializer short-circuits on $ref and drops sibling $defs / $dynamicAnchor declarations entirely (Bug: OpenAPI 3.1 $ref siblings ($defs, $dynamicAnchor, $id) are silently dropped when parsing OpenAPI.NET#2895). The binding information never reaches Kiota.
What's already in place (this is smaller than it looks)
The CodeDOM already supports generics as type arguments:
CodeType.GenericTypeParameterValues (src/Kiota.Builder/CodeDOM/CodeType.cs:43-55) is consumed by the C#, TypeScript, Go, Python, and Dart convention services for <T> / [T] emission.
CommonLanguageRefiner.MoveRequestBuilderPropertiesToBaseType(..., addCurrentTypeAsGenericTypeParameter: true) (src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs:1446-1464) already emits class FooRequestBuilder : BaseRequestBuilder<FooRequestBuilder> for every RequestBuilder today — precedent for parameterized base-class emission.
RemoveRequestConfigurationClasses + GetGenericTypeForRequestConfiguration is a second precedent.
The narrow gap for full generic support: ProprietableBlockDeclaration can carry generic arguments on Inherits/Implements but cannot declare its own unbound type parameters (class PaginatedTemplate<T> where T is the class's parameter rather than a bound argument). Adding a TypeParameters collection resolves this.
Reference implementation
Orval shipped this in May 2026 — PR orval-labs/orval#3353. It emits interface PaginatedTemplate<itemType> + type PaginatedUserResponse = PaginatedTemplate<User>, preserving all 7 fixtures across all 4 OAS versions.
Why this matters
All Kiota-generated SDKs, including the Microsoft Graph SDK, lose type safety for any API that uses these patterns.
OpenAPI 3.1.x adopts JSON Schema 2020-12 as its schema dialect; Kiota already claims 3.1 / 3.2 support.
Known limitation: when a base type declaring $dynamicAnchor is materialized standalone before any derived type (e.g. it's the response of another endpoint), dynamic-ref-typed properties resolve against the base instead of the derived. The fallback is UntypedNode with a build-time warning. Addressed in Phase 3.
This phase preserves type safety by emitting concrete bound models where needed, such as PaginatedTemplateUser and PaginatedTemplateGroup. This is an interim implementation and fallback path. It does not emit reusable generic templates like PaginatedTemplate<TItem>.
Phase 3: Multi-derivation dynamic-ref resolution — when multiple schemas declare the same $dynamicAnchor and no single active binding exists, emit a union/wrapper over viable candidates instead of resolving to whichever model materializes first. PR: feat: resolve $dynamicRef generic bindings with per-context classes #7978 (merged).
TypeScript/Python can use native unions; C#/Java/Go/PHP/Dart/Ruby can use the existing IComposedTypeWrapper pattern via CodeUnionType + ConvertUnionTypesToWrapper.
This phase is independent of reusable generic template emission.
Phase 4: Reusable generic template emission — emit true reusable generic templates for languages that support them, e.g. PaginatedTemplate<TItem> with bound operation types such as PaginatedTemplate<User> / PaginatedTemplate<Group>.
This is separate from Phase 2 because it requires cross-language CodeDOM and writer work:
Generic type parameter declaration support (class Foo<T>, type Foo[T ...] struct, etc.).
Marking $dynamicAnchor placeholders as type parameters.
Generic template model declarations.
Bound aliases/usages where appropriate.
A per-language deserialization strategy, likely using generated bound factory helpers or wrappers.
Concrete bound models as fallback for languages where generic model emission is not practical.
$dynamicRef / $dynamicAnchor Support for OpenAPI 3.1+
Summary
Kiota silently degrades OpenAPI 3.1+ schemas that use JSON Schema 2020-12
$dynamicRef/$dynamicAnchor(spec §7.7) toUntypedNode, which becomesobject/any/unknownacross all output languages. No error, no warning. The upstream parser (Microsoft.OpenApi3.7.0) already parses and exposes both keywords onIOpenApiSchema, but Kiota's code-generation pipeline never reads them.This affects two real-world uses:
LocalizedCategory.childrenshould beLocalizedCategory[]but generates asunknown[].PaginatedTemplate<T>withPaginatedUserResponse = PaginatedTemplate<User>andPaginatedGroupResponse = PaginatedTemplate<Group>. Kiota currently can't emit the reusable template; consumers get duplicated concrete wrappers or untypeditems.Reproduction
Minimal fixtures (small, single-purpose) are available in the tracker repo:
Running any of these through
kiota generate -d <fixture> -l typescriptreproduces the issue.Root cause (verified against
mainHEAD3939d526d)DynamicRef/DynamicAnchoranywhere in the Kiota tree (source + tests).KiotaBuilder.CreateModelDeclarations(src/Kiota.Builder/KiotaBuilder.cs:1900) dispatches onIsReferencedSchema()(which only matchesOpenApiSchemaReference, i.e.$ref), inheritance, intersection, union, object, array, primitive — a schema whose only content is{ $dynamicRef: '#foo' }matches none of these and falls through to the UntypedNode fallback atKiotaBuilder.cs:1977.Microsoft.OpenApi3.7.0 exposesIOpenApiSchema.DynamicRef/IOpenApiSchema.DynamicAnchor, andOpenApiSchemaReferencedelegates both from itsTarget. Phase 1 is unblocked on this front — Kiota just needs to read these properties. Phase 2 has a deeper upstream issue: the 3.1 deserializer short-circuits on$refand drops sibling$defs/$dynamicAnchordeclarations entirely (Bug: OpenAPI 3.1$refsiblings ($defs,$dynamicAnchor,$id) are silently dropped when parsing OpenAPI.NET#2895). The binding information never reaches Kiota.What's already in place (this is smaller than it looks)
The CodeDOM already supports generics as type arguments:
CodeType.GenericTypeParameterValues(src/Kiota.Builder/CodeDOM/CodeType.cs:43-55) is consumed by the C#, TypeScript, Go, Python, and Dart convention services for<T>/[T]emission.CommonLanguageRefiner.MoveRequestBuilderPropertiesToBaseType(..., addCurrentTypeAsGenericTypeParameter: true)(src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs:1446-1464) already emitsclass FooRequestBuilder : BaseRequestBuilder<FooRequestBuilder>for every RequestBuilder today — precedent for parameterized base-class emission.RemoveRequestConfigurationClasses+GetGenericTypeForRequestConfigurationis a second precedent.The narrow gap for full generic support:
ProprietableBlockDeclarationcan carry generic arguments onInherits/Implementsbut cannot declare its own unbound type parameters (class PaginatedTemplate<T>where T is the class's parameter rather than a bound argument). Adding aTypeParameterscollection resolves this.Reference implementation
Orval shipped this in May 2026 — PR orval-labs/orval#3353. It emits
interface PaginatedTemplate<itemType>+type PaginatedUserResponse = PaginatedTemplate<User>, preserving all 7 fixtures across all 4 OAS versions.Why this matters
$dynamicRefis the spec-blessed mechanism for it.Roadmap
Phase 1: Recursive dynamic-scope resolution — schemas with
$dynamicAnchor+ nested$dynamicRef(e.g. category trees). PR: feat: resolve $dynamicRef against $dynamicAnchor for recursive types #7817. Verified across C#, TypeScript, Go, Python, Java via integration tests.$dynamicAnchoris materialized standalone before any derived type (e.g. it's the response of another endpoint), dynamic-ref-typed properties resolve against the base instead of the derived. The fallback isUntypedNodewith a build-time warning. Addressed in Phase 3.Phase 2: Binding-aware
$dynamicRefmaterialization — resolve$dynamicRefbindings supplied by$defs/$dynamicAnchorat the usage site. PR: feat: resolve $dynamicRef generic bindings with per-context classes #7978 (merged).Scope:
UntypedNode.This phase preserves type safety by emitting concrete bound models where needed, such as
PaginatedTemplateUserandPaginatedTemplateGroup. This is an interim implementation and fallback path. It does not emit reusable generic templates likePaginatedTemplate<TItem>.Phase 3: Multi-derivation dynamic-ref resolution — when multiple schemas declare the same
$dynamicAnchorand no single active binding exists, emit a union/wrapper over viable candidates instead of resolving to whichever model materializes first. PR: feat: resolve $dynamicRef generic bindings with per-context classes #7978 (merged).TypeScript/Python can use native unions; C#/Java/Go/PHP/Dart/Ruby can use the existing
IComposedTypeWrapperpattern viaCodeUnionType+ConvertUnionTypesToWrapper.This phase is independent of reusable generic template emission.
Phase 4: Reusable generic template emission — emit true reusable generic templates for languages that support them, e.g.
PaginatedTemplate<TItem>with bound operation types such asPaginatedTemplate<User>/PaginatedTemplate<Group>.This is separate from Phase 2 because it requires cross-language CodeDOM and writer work:
class Foo<T>,type Foo[T ...] struct, etc.).$dynamicAnchorplaceholders as type parameters.Suggested sub-phases:
CodeTypeParameter,TypeParameters,IsGeneric). PR: feat: resolve $dynamicRef generic bindings with per-context classes #7978 (merged).Willing to contribute
Phase 1 PR: #7817. Phase 3 to follow; Phase 2 follows once microsoft/OpenAPI.NET#2895 lands.
Context
I'm doing this as part of my effort to expand dynamicRef across the ecosystem. Progress is tracked in https://github.com/aqeelat/openapi-dynamicref-adoption-tracker
This issue was drafted with assistance from AI tooling. The submitter is responsible for reviewing and validating the contents before submission.