ObjectResult - missing discriminator from System.Text.Json polymorphism
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
### Is there an existing issue for this?
- [X] I have searched the existing issues
### 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.
```csharp
[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; }
}
```
```csharp
[HttpGet("[action]")]
public ActionResult GetRandomPerson()
{
Person person = GetPerson(); // returns either a PrivatePerson or LegalPerson
return Ok(person);
}
```
JSON output:
```json
{
"firstName": "Test",
"id": 1
}
```
Note how there is **no discriminator**
Expected JSON output:
```json
{
"$type": "PrivatePerson",
"firstName": "Test",
"id": 1
}
```
---
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:
```csharp
[HttpGet("[action]")]
public ActionResult Ok1()
{
Person person = GetPerson();
return new OkObjectResult(person) { DeclaredType = typeof(Person) };
}
[HttpGet("[action]")]
public ActionResult Ok2()
{
Person person = GetPerson();
return person;
}
```
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 resolve `DeclaredType = 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_
Contributor guide
Assessment
This issue has not been assessed yet.