With .NET 7.0 being released, System.Text.Json
is supposed to support polymorphic code. Unfortunately, it seems like it can't be used out of the box when you need to return a derived type's instance from a controller's method. For instance, suppose the following model:
public class Base {}
public class Derived1: Base { }
public class Derived2: Base { }
Suppose also that we've got the following dynamic type info resolver:
public class JsonHierarchyTypeInfoResolver : DefaultJsonTypeInfoResolver {
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) {
var jsonTypeInfo = base.GetTypeInfo(type, options);
if( typeof(Base) == jsonTypeInfo.Type ) {
jsonTypeInfo.PolymorphismOptions = new( ) {
TypeDiscriminatorPropertyName = "$type",
IgnoreUnrecognizedTypeDiscriminators = true,
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
DerivedTypes = {
new JsonDerivedType(typeof(Derived1), typeof(Derived1).AssemblyQualifiedName!),
new JsonDerivedType(typeof(Derived2), typeof(Derived2).AssemblyQualifiedName!)
}
};
}
return jsonTypeInfo;
}
}
And then, it's used in the app by doing something like this:
builder.Services.AddControllers( )
.AddJsonOptions(options => {
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
options.JsonSerializerOptions.TypeInfoResolver = new JsonHierarchyTypeInfoResolver( );
});
Let's also assume that we've got a simple controller with a simple method that looks like this:
[ApiController]
[Route("[controller]")]
public class DEMOController : ControllerBase {
[HttpGet]
public ActionResult<Base> GetAsync() {
var derived = new Derived1( );
return Ok(derived);
}
}
Whenever the method is called, it will not generate json with the type descriminator as I expected it to. It seems like the problem lies with the SystemTextJsonOutputFormatter
when it tries to serialize the object with the following code:
await JsonSerializer.SerializeAsync(responseStream, context.Object, objectType, SerializerOptions, httpContext.RequestAborted);
and here's how objectType
is initialized:
// context.ObjectType reflects the declared model type when specified.
// For polymorphic scenarios where the user declares a return type, but returns a derived type,
// we want to serialize all the properties on the derived type. This keeps parity with
// the behavior you get when the user does not declare the return type and with Json.Net at least at the top level.
var objectType = context.Object?.GetType() ?? context.ObjectType ?? typeof(object);
Since the method uses the derived type, the custom info type resolver won't be able to do its magic. Am I missing something? Is this a known issue? Does this mean that I should keep using json.net instead of trying to migrate to System.Text.Json?