We have encountered some odd behaviour when migrating our code from Newtonsoft.Json to System.Text.Json, and the scenario doesn't seem to be well documented.
[JsonDerivedType(typeof(LegalPerson), nameof(LegalPerson))]
[JsonDerivedType(typeof(PrivatePerson), nameof(PrivatePerson))]
public abstract class Person
{
public int Id { get; set; }
}
public class PrivatePerson : Person
{
public string? FirstName { get; set; }
}
public class LegalPerson : Person
{
public string? ContractName { get; set; }
}
[HttpGet("[action]")]
public ActionResult<Person> GetRandomPerson()
{
Person person = GetPerson(); // returns either a PrivatePerson or LegalPerson
return Ok(person);
}
The issue is that Ok(object?) becomes new OkObjectResult(object?) { DeclaredType = null }.
Later during the serialization, DeclaredType = person.GetType(), which in this case ends up being either typeof(PrivatePerson) or typeof(LegalPerson).
Since we are not serializing a Person, but the underlying type, the discriminator is not included.
Our contract clearly state that this endpoint returns a Person, but there is no way for the consumer to know how to deserialize the result without any discriminator.
The same issue applies for other ObjectResults such as Created, CreatedAtResult etc.
The solution is resolved if we write the endpoints like this:
[HttpGet("[action]")]
public ActionResult<Person> Ok1()
{
Person person = GetPerson();
return new OkObjectResult(person) { DeclaredType = typeof(Person) };
}
[HttpGet("[action]")]
public ActionResult<Person> Ok2()
{
Person person = GetPerson();
return person;
}
The expected behaviour is for the discriminator to be included. Newtonsoft always seems to include the discriminator, but there is no way for us to enable that in System.Text.Json.
Another potential solution would be for ObjectResult to take a generic type argument to resolve DeclaredType = typeof(T).
I found that this issue has been raised before in different context such as #44852 but from what I can tell, the comments implies that this should have been resolved already.
Is there an existing issue for this?
Describe the bug
We have encountered some odd behaviour when migrating our code from Newtonsoft.Json to System.Text.Json, and the scenario doesn't seem to be well documented.
JSON output:
Note how there is no discriminator
Expected JSON output:
The issue is that
Ok(object?)
becomesnew OkObjectResult(object?) { DeclaredType = null }
. Later during the serialization,DeclaredType = person.GetType()
, which in this case ends up being eithertypeof(PrivatePerson)
ortypeof(LegalPerson)
.Since we are not serializing a
Person
, but the underlying type, the discriminator is not included. Our contract clearly state that this endpoint returns aPerson
, but there is no way for the consumer to know how to deserialize the result without any discriminator.The same issue applies for other ObjectResults such as
Created
,CreatedAtResult
etc.The solution is resolved if we write the endpoints like this:
However, the documentation found in https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-8.0 doesn't seem to mention this.
Expected Behavior
The expected behaviour is for the discriminator to be included. Newtonsoft always seems to include the discriminator, but there is no way for us to enable that in System.Text.Json. Another potential solution would be for
ObjectResult
to take a generic type argument to resolveDeclaredType = typeof(T)
.Steps To Reproduce
https://github.com/yesmey/PolymorphismBug
Exceptions (if any)
No response
.NET Version
8.0.400 and 9.0.100-preview.7
Anything else?
No response