Skip to content

JSON schema serialization problems at OpenApi schema generation #113610

Closed
@vkuharsky

Description

@vkuharsky

Description

Hello, I'm currently upgrading from .NET 6 to .NET 9. Everything goes fine, but JSON serialization. I'm trying to use the default source generation option for better performance but ran into problems during OpenAPI document construction.

An application I'm developing uses controllers to provide a set of APIs to its clients. Since the start of an upgrade process, I've decided to create a set of small and focused serializer contexts rather than one big and clumsy context per application. This decision resulted in around 39 source generator contexts, which I combine during the startup phase by using TypeInfoResolverChain.Add method. Everything compiles fine, but when I request the OpenApi document via a browser's GET at /openapi/v1.json I get a NotSupportedException error.

All models used inside of the API controllers are public sealed classes, using only .NET types (ints, booleans, Guids, etc.), and do not reference any custom objects as their property members. The type displayed in that error was correctly included in its respective serialization context class:

Image
That context later combines with other 38 contexts via:

Image
During a debug session I check if the TyperInfoRsolverChain contains the context needed for successful serialization of the requested object's schema.
Here is an immediate window's output:

? options.JsonSerializerOptions.TypeInfoResolverChain
Count = 39, IsReadOnly = False
...
[24]: {Tntu.Automated.ServicesLayer.Rest.WebApi.Controllers.Worker.PersonWorkModelsContext}
...

?options.JsonSerializerOptions.TypeInfoResolverChain[24]
{Tntu.Automated.ServicesLayer.Rest.WebApi.Controllers.Worker.PersonWorkModelsContext}
    DateTime: Type = DateTime, Kind = None
    Decimal: Type = Decimal, Kind = None
    GeneratedSerializerOptions: TypeInfoResolver = <null>, IsReadOnly = False
    Guid: Type = Guid, Kind = None
    Int32: Type = Int32, Kind = None
    NullableDateTime: Type = Nullable`1, Kind = None
    NullableDecimal: Type = Nullable`1, Kind = None
    NullableInt32: Type = Nullable`1, Kind = None
    Options: TypeInfoResolver = Tntu.Automated.ServicesLayer.Rest.WebApi.Controllers.Worker.PersonWorkModelsContext, IsReadOnly = True
    PersonWorkCreateParams: Type = PersonWorkCreateParams, Kind = Object
    PersonWorkEditParams: Type = PersonWorkEditParams, Kind = Object
    PersonWorkEntity: Type = PersonWorkEntity, Kind = Object
    String: Type = String, Kind = None
    _DateTime: Type = DateTime, Kind = None
    _Decimal: Type = Decimal, Kind = None
    _Guid: Type = Guid, Kind = None
    _Int32: Type = Int32, Kind = None
    _NullableDateTime: Type = Nullable`1, Kind = None
    _NullableDecimal: Type = Nullable`1, Kind = None
    _NullableInt32: Type = Nullable`1, Kind = None
    _PersonWorkCreateParams: Type = PersonWorkCreateParams, Kind = Object
    _PersonWorkEditParams: Type = PersonWorkEditParams, Kind = Object
    _PersonWorkEntity: Type = PersonWorkEntity, Kind = Object
    _String: Type = String, Kind = None

What can go wrong here? Does the Microsoft.AspNetCore.OpenApi package use source generators or do I need to revert back to a reflection mechanism?

Expected behavior

Contents of v1.json is up and ready

Actual behavior

Error message:
NotSupportedException: JsonTypeInfo metadata for type 'Tntu.Automated.ServicesLayer.Rest.WebApi.Models.Output.Worker.PersonWorkEntity' was not provided by TypeInfoResolver of type 'System.Text.Json.Serialization.Metadata.JsonTypeInfoResolverWithAddedModifiers'. If using source generation, ensure that all root types passed to the serializer have been annotated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.

Update 3/17/2025 3:27 PM

Full stack trace:

System.Text.Json.ThrowHelper.ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver resolver)
System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, bool ensureConfigured, Nullable<bool> ensureNotNull, bool resolveIfMutable, bool fallBackToNearestAncestorType)
System.Text.Json.Schema.JsonSchemaExporter.GetJsonSchemaAsNode(JsonSerializerOptions options, Type type, JsonSchemaExporterOptions exporterOptions)
Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.CreateSchema(OpenApiSchemaKey key)
System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey key, Func<TKey, TValue> valueFactory)
Microsoft.AspNetCore.OpenApi.OpenApiSchemaStore.GetOrAdd(OpenApiSchemaKey key, Func<OpenApiSchemaKey, JsonNode> valueFactory)
Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.GetOrCreateSchemaAsync(Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, bool captureSchemaByRef, CancellationToken cancellationToken)
Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetResponseAsync(ApiDescription apiDescription, int statusCode, ApiResponseType apiResponseType, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetResponsesAsync(ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationAsync(ApiDescription description, HashSet<OpenApiTag> capturedTags, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping<string, ApiDescription> descriptions, HashSet<OpenApiTag> capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(HashSet<OpenApiTag> capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)
Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions+<>c__DisplayClass0_0+<<MapOpenApi>b__0>d.MoveNext()
Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F56B68D2B55B5B7B373BA2E4796D897848BC0F04A969B1AF6260183E8B9E0BAF2__GeneratedRouteBuilderExtensionsCore+<>c__DisplayClass2_0+<<MapGet0>g__RequestHandler|5>d.MoveNext()
Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions