Home > Net >  Polymorphic type in a collection in WebAPI
Polymorphic type in a collection in WebAPI

Time:10-19

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.

  • Related