I have a custom generic type that looks roughly like this:
public struct Foo<T>
{
public int Value { get; }
public string Signature { get; }
public Type Type { get; }
}
This type is is used in request and response bodies and in controller action parameters. Everything is configured so that it's serialized as a string, and it works fine with model binding and JSON serialization. The type has a TypeConverter
associated with it, which takes care of converting it to and from a string.
However, the Swagger schema still represents it as an object with 3 properties. The Type
property is also expanded, which pulls all the System.Reflection
types exposed directly or indirectly by Type
.
How can I avoid this and expose my type as a string?
First solution attempted: using MapType
I tried to use MapType
; it works fine if I specify the generic type argument, but doesn't work with the open generic type:
c.MapType(typeof(Foo<Something>), () => new OpenApiSchema { Type = "string" }); // Works
c.MapType(typeof(Foo<>), () => new OpenApiSchema { Type = "string" }); // Doesn't work
How can I apply the mapping to Foo<T>
, for any T
?
Current workaround
So far the only workaround I have is pretty ugly :
class SchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type is Type type &&
type.IsGenericType &&
!type.IsGenericTypeDefinition &&
type.GetGenericTypeDefinition() == typeof(Foo<>))
{
schema.Type = "string";
schema.Properties.Clear();
}
else if (context.Type?.FullName.StartsWith("System.", StringComparison.Ordinal) is true
&& context.SchemaRepository.TryGetIdFor(context.Type, out var schemaId))
{
DocFilter.SchemaIdsToRemove.Add(schemaId);
}
}
}
class DocFilter : IDocumentFilter
{
public static readonly HashSet<string> SchemaIdsToRemove = new HashSet<string>();
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var schemaId in SchemaIdsToRemove)
{
swaggerDoc.Components.Schemas.Remove(schemaId);
}
}
}
i am not sure what you are trying to do. Because the best thing if i understood your scenario correctly would be to expose one schema definition for each underlying type of the Foo generic type, code then would be something like:
public class FooSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type.IsGenericType && context.Type.GetGenericTypeDefinition() == typeof(Foo<>))
{
var argumentType = context.Type.GetGenericArguments().First();
var argumentSchema = context.SchemaGenerator.GenerateSchema(argumentType, context.SchemaRepository);
var baseSchemaName = $"{argumentType.Name}Foo";
var baseSchema = new OpenApiSchema()
{
Required = new SortedSet<string>() { "type" },
Type = "object",
Properties = new Dictionary<string, OpenApiSchema> {
{ "type", argumentSchema }
};
context.SchemaRepository.AddDefinition(baseSchemaName, baseSchema);
schema.Type = "string";
schema.Reference = new OpenApiReference { Id = $"{baseSchemaName}", Type = ReferenceType.Schema };
}
}
}
if you need the other properties as well then include them in the base schema. This will create a new schema for every type but it should deserialize into your generic type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With