ASP.NET Core 6 Web API with C#. I want a REST endpoint with a collection of a polymorphic type:
app.MapGet("/api/temp", Temp);
//...
static Foo Temp()
{
return new Foo(new Base[] { new Derived(10, 20) });
}
public abstract record Base(int a);
public record Derived(int b, int a): Base(a);
public record Foo(Base[] data);
If invoked, the method returns:
{ "data": [ {"a": 20} ] }
as if the array elements are being serialized according to the array's declaration type as opposed to the run-time type.
Tried with List<Base>
, same result.
Any way around that, please? When the return type itself is polymorphic, the Web API JSON serialization logic respects that.
EDIT: one crude workaround involves declaring Foo.data
as object[]
. But that will kill the Swagger type annotation - unacceptable in my case.
EDIT: deceived Swagger by providing the [ProducesResponseType(typeof(RealFoo), 200)]
annotation on the Web method.
CodePudding user response:
It will work if you enable polymorphism in Swagger
builder.Services.AddSwaggerGen(c => c.UseOneOfForPolymorphism());
and create a custom converter. Please note that this is really a working sample based on your model
public class CustomConverter : JsonConverter<Base>
{
public override Base Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// omitted ...
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, Base baseModel, JsonSerializerOptions options)
{
writer.WriteStartObject();
if (baseModel is Derived derived)
{
writer.WriteNumber("a", derived.a);
writer.WriteNumber("b", derived.b);
}
writer.WriteEndObject();
}
}
Register converter as
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.Converters.Add(new CustomConverter());
});
More information here How to write custom converters for JSON serialization (marshalling) in .NET
Consider also comments about Newtonsoft.Json
CodePudding user response:
I hear what other people are saying, but the minimal way to get to where I wanted was:
app.MapGet("/api/temp", Temp);
//...
[ProducesResponseType(typeof(Foo), 200)]
static OFoo Temp()
{
return new OFoo(new Base[] { new Derived(10, 20) });
}
public abstract record Base(int a);
public record Derived(int b, int a): Base(a);
public record Foo(Base[] data);
public record OFoo(object[] data);
Notably, this approach is isolated to the method(s) where the collection of polymorphic elements exists, the amount of typing required is O(1) relative to the number of desired subtypes of Base
, and generally doesn't depend on the complexity of the collection element type tree. In the real project of mine it's considerably more involved than Base
/Derived
with one int
each.