Search code examples
c#jsonasp.net-coresystem.text.json.net-7.0

JSON Polymorphic serialization in .NET7 Web API


.NET7 Includes a lot of improvements for System.Text.Json serializer, one of which is polymorphic serialization of types using the new [JsonPolymorphic] attribute. I am trying to use it in my Asp.Net web API, however it doesn't seem to serialize the type discriminator despite the fact that the model is properly setup.

It only happens when trying to send the objects over the wire, when using JsonSerializer, everything appears to be working well. For example:

// This is my data model
[JsonPolymorphic]
[JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
[JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
public abstract record Result;
public record SuccesfulResult : Result;
public record ErrorResult(string Error) : Result;
// Some test code that actually works
var testData = new Result[]
{
    new SuccesfulResult(),
    new ErrorResult("Whoops...")
};

var serialized = JsonSerializer.SerializeToDocument(testData);
// Serialized string looks like this:
// [{ "$type": "ok" }, { "$type": "fail", "error": "Whoops..." }]
// So type discriminators are in place and all is well
var deserialized = serialized.Deserialize<IEnumerable<Result>>()!;

// This assertion passes succesfully!
// We deserialized a collection polymorphically and didn't lose any type information.
testData.ShouldDeepEqual(deserialized);
// However, when sending the object as a response in an API, the AspNet serializer
// seems to be ignoring the attributes:

[HttpGet("ok")]
public Result GetSuccesfulResult() => new SuccesfulResult();

[HttpGet("fail")]
public Result GetFailResult() => new ErrorResult("Whoops...");

Neither of these responses are annotated with a type discriminator and my strongly-typed clients can't deserialize the results into a proper hierarchy.

GET /api/ok HTTP/1.1
# =>
# ACTUAL: {}
# EXPECTED: { "$type": "ok" }
GET /api/fail HTTP/1.1
# =>
# ACTUAL: { "error": "Whoops..." }
# EXPECTED: { "$type": "fail", "error": "Whoops..." }

Am I missing some sort of API configuration that would make controllers serialize results in a polymorphic manner?


Solution

  • Specifying [JsonDerivedType(...)] on individual subclasses and on the base type seems to resolve an issue but barely seems intentional. This possibly might be fixed in future releases.

    [JsonPolymorphic]
    [JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
    [JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
    public abstract record Result;
    
    [JsonDerivedType(typeof(SuccesfulResult), typeDiscriminator: "ok")]
    public record SuccesfulResult : Result;
    
    [JsonDerivedType(typeof(ErrorResult), typeDiscriminator: "fail")]
    public record ErrorResult(string Error) : Result;