Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Provide more details on Type hierarchy when generation fails #2722

Open
dougclutter opened this issue Oct 4, 2023 · 3 comments · May be fixed by #2723
Open

Feature Request: Provide more details on Type hierarchy when generation fails #2722

dougclutter opened this issue Oct 4, 2023 · 3 comments · May be fixed by #2723

Comments

@dougclutter
Copy link

We've started using System.Text.Json source-code generation and it has basically killed our Swagger page. We're now getting the following from the Swashbuckle code that generates the JSON:

Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate Operation for action - Microsoft.AspNetCore.OData.Routing.Controllers.MetadataController.GetMetadata (Microsoft.AspNetCore.OData). See inner exception
 ---> Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Failed to generate schema for type - Microsoft.OData.Edm.IEdmModel. See inner exception
 ---> System.NotSupportedException: Metadata for type 'System.Object' was not provided by TypeInfoResolver of type 'UniversityFullStack.Shared.Models.UniversitySerializerContext'. 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 System.Text.Json.JsonSerializer.GetTypeInfo[T](JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Serialize[TValue](TValue value, JsonSerializerOptions options)
   at Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver.JsonConverterFunc(Object value)
   at Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver.GetDataContractForType(Type type)
   at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GetDataContractFor(Type modelType)
   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.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__3()
   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.SchemaGenerator.GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
   at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.CreateArraySchema(DataContract dataContract, SchemaRepository schemaRepository)
   at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__1()
   at Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GenerateConcreteSchema(DataContract dataContract, SchemaRepository schemaRepository)
   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.<>c__DisplayClass10_0.<GenerateConcreteSchema>b__3()
   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.SchemaGenerator.GenerateSchema(Type modelType, SchemaRepository schemaRepository, MemberInfo memberInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
   at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateSchema(Type type, SchemaRepository schemaRepository, PropertyInfo propertyInfo, ParameterInfo parameterInfo, ApiParameterRouteInfo routeInfo)
   --- End of inner exception stack trace ---

As you can see, it's getting hung up because it thinks we're serializing a System.Object; we aren't. Unfortunately, the error message provided doesn't give us any clues about which object caused the exception.

Let's say we had classes like this:

public class MyClass {
  public object MyProperty { get; set; }
}
public class MyReturnedClass {
  public MyClass RetValue { get; set; }
}

Instead of this message:

Metadata for type 'System.Object' was not provided by TypeInfoResolver 
of type 'UniversityFullStack.Shared.Models.UniversitySerializerContext'. 

It would be much more helpful to get a message showing the context of what the generator is working on. Maybe something like this:

Metadata for type 'System.Object' was not provided by TypeInfoResolver 
of type 'UniversityFullStack.Shared.Models.UniversitySerializerContext' 
for property MyClass.MyProperty
used by MyReturnedClass.RetValue.
@dougclutter
Copy link
Author

Did a little more research on this and found an easy fix for adding the additional context. Changed the Swashbuckle.AspNetCore.SwaggerGen.SchemaGenerator.GetDataContractFor method to include a try-catch like this:

private DataContract GetDataContractFor(Type modelType)
{
    try
    {
        var effectiveType = Nullable.GetUnderlyingType(modelType) ?? modelType;
        return _serializerDataContractResolver.GetDataContractForType(effectiveType);
    }
    catch (Exception ex)
    {
        throw new SwaggerGeneratorException(
            message: $"Failed to generate data contract for type - {modelType}. See inner exception",
            innerException: ex);
    }
}

This pointed me to a problem with how Swashbuckle is handling enums. Here's a simple Console program that documents the problem:

using System.Text.Json;
using System.Text.Json.Serialization;

var options = new JsonSerializerOptions { TypeInfoResolver = MyJsonSerializerContext.Default };

// Serializing without JsonSerializerContext works
Console.WriteLine(JsonSerializer.Serialize(DrinkSize.Small));

// Serializing with JsonSerializerContext works
Console.WriteLine(JsonSerializer.Serialize(DrinkSize.Small, options));

// Swashbuckle uses this approach to get the first enum value
var enumValues = typeof(DrinkSize).GetEnumValues();
var firstValue = enumValues.GetValue(0);
// Using dynamic works
// dynamic firstValue = enumValues.GetValue(0);

// Serializing without JsonSerializerContext works
Console.WriteLine(JsonSerializer.Serialize(firstValue));

// Since firstValue is a System.Object instead of a DrinkSize, this fails
Console.WriteLine(JsonSerializer.Serialize(firstValue, options));


public enum DrinkSize
{
    Small,
    Medium,
    Large
}

[JsonSerializable(typeof(DrinkSize))]
public partial class MyJsonSerializerContext : JsonSerializerContext
{
}

The fix for this was even easier. Changed Swashbuckle.AspNetCore.SwaggerGen.JsonSerializerDataContractResolver.JsonConverterFunc to take a dynamic parameter instead of an object:

private string JsonConverterFunc(dynamic value)
{
    return JsonSerializer.Serialize(value, _serializerOptions);
}

I'll wrap both changes in a pull request and get it submitted.

Copy link
Contributor

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

@github-actions github-actions bot added the stale Stale issues or pull requests label Apr 14, 2024
@martincostello martincostello removed the stale Stale issues or pull requests label Apr 14, 2024
@martincostello
Copy link
Collaborator

#2723 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants