Swashbuckle.AspNetCore
Swashbuckle.AspNetCore copied to clipboard
Exception when using System.Text.Json version 7 with System.Text.Json source generator and enums
I am using Swashbuckle.AspNetCore version 6.5.0. I have created a class inherited from JsonSerializerContext to use System.Text.Json source generator, and added the context at startup:
builder.Services.AddControllers()
.AddJsonOptions(options => { options.JsonSerializerOptions.AddContext<ApiJsonContext>(); });
The ApiJsonContext looks like this:
[JsonSerializable(typeof(WeatherForecast))]
public partial class ApiJsonContext : JsonSerializerContext
{
}
I have a controller that returns a simple object with an enum:
public class WeatherForecast
{
public WeatherType WeatherType { get; set; }
}
public enum WeatherType
{
Unknown = 0,
Bad,
Nice
}
This works great when using System.Text.Json version 6, but breaks with this error if I upgrade to System.Text.Json version 7:
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate Operation for action - WebApplicationTest.Controllers.WeatherForecastController.Get (WebApplicationTest). See inner exception
---> Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate schema for type - WebApplicationTest.WeatherForecast. See inner exception
---> System.NotSupportedException: Metadata for type 'System.Object' was not provided by TypeInfoResolver of type 'WebApplicationTest.ApiJsonContext'. If using source generation, ensure that all root types passed to the serializer have been indicated with 'JsonSerializableAttribute', along with any types that might be serialized polymorphically.
at System.Text.Json.ThrowHelper.ThrowNotSupportedException_NoMetadataForType(Type type, IJsonTypeInfoResolver resolver)
at System.Text.Json.JsonSerializerOptions.GetTypeInfoInternal(Type type, Boolean ensureConfigured, Boolean resolveIfMutable)
at System.Text.Json.JsonSerializerOptions.get_ObjectTypeInfo()
at Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver.GetDataContractForType(Type type)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForMember(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, DataProperty dataProperty)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateObjectSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateReferencedSchema(DataContract dataContract, SchemaRepository schemaRepository, Func`1 definitionFactory)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateSchemaForType(Type modelType, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.CreateResponseMediaType(ModelMetadata modelMetadata, SchemaRepository schemaRespository)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateResponse(ApiDescription apiDescription, SchemaRepository schemaRepository, String statusCode, ApiResponseType apiResponseType)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateResponses(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
--- End of inner exception stack trace ---
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperation(ApiDescription apiDescription, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable`1 apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerDocumentWithoutFilters(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwaggerAsync(String documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
This error occurs only if using enum. No error if I change to string:
public class WeatherForecast
{
public string WeatherType { get; set; }
}
Hello Same for me any workaround?
Same issue also. Getting ready for net8, and moving to the source generation.
the type is an enum, Stack Trace shows SwaggerGen/Swashbuckle has the type:
I think the fault falls here though. JsonConverterFunc takes the first value of the enum, and it becomes an object (boxed?). Shouldn't it be the type
passed in?
I have not tried rolling this back to package release System.Text.Json
6.x yet, using 7.0.3. Have not tried the RC2 of v8 yet.
In short - using the System.Test.Json Serialization Source Gen, this will cause the throw.
As a work around -
[JsonSerializable(typeof(object))]
on top of your JsonSerializerContext
will get you past it.
For the fix:
On this line:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/8f363f7359cb1cb8fa5de5195ec6d97aefaa16b3/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs#L46C24-L46C41
Have it call a new function with the type passed in
private string JsonConverterFunc(object value, Type t, JsonSerializerOptions options)
{
return JsonSerializer.Serialize(value, t, options);
}
In case this helps anyone else, in my case for some reason the JsonSerializerDataContractResolver
that was getting used by Swashbuckle to generate the swagger.json file was defaulting to a brand new JsonSerializerOptions, rather than correctly fetching the JsonSerializerOptions I'd already configured via a call to ConfigureHttpJsonOptions
during startup.
The workaround for me, so far working, has been to explicitly add ISerializerDataContractResolver
to the DI container before AddSwaggerGen
is called so that Swashbuckle is using the same JsonSerializerContext
as the rest of my application which is correctly configured with all the enums and other types I use in my API - though I'm not sure why it wasn't in the first place (possibly bad code our side):
services.AddTransient<ISerializerDataContractResolver>(sp =>
{
var opts = sp.GetRequiredService<IOptions<JsonOptions>>().Value?.SerializerOptions
?? new JsonSerializerOptions(JsonSerializerDefaults.Web);
return new JsonSerializerDataContractResolver(opts);
});
This works in DotSwashbuckle, tested in 3.0.9
Thanks for the hints here in the thread.
JsonSerializerDataContractResolver
seems to me to have fundamental issues with AoT compatibility with regards to be supported for all possible scenarios (or at least would need to be heavily annotated). It may however work for various simple use cases.
I've applied the suggestion from https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2593#issuecomment-1799837881 to #2800.
The reason https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2593#issuecomment-1836432544 doesn't work out of the box is because Swashbuckle hadn't been taught to understand the other JsonOptions
class that is specific to Minimal APIs (as opposed to MVC). While explicit configuration is the way to go to explicitly configure which options you want to use, I've updated the code in #2799 to fall through to the options for Minimal APIs if not resolvable from MVC.