Hello everyone, I am the Wolf at the End of the Desert.
1. Problem Description
As shown below, define two subclasses Student and Employ, both inheriting from the abstract class PersonBase:
public abstract class PersonBase
{
public string Name { get; set; }
protected PersonBase(string name)
{
Name = name;
}
}
public class Student : PersonBase
{
public string Number { get; set; }
public Student(string name, string number) : base(name)
{
Number = number;
}
}
public class Employ : PersonBase
{
public string CompanyName { get; set; }
public Employ(string name, string companyName) : base(name)
{
CompanyName = companyName;
}
}
Add a Web API endpoint that returns a collection of the base class:
[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
[HttpGet(Name = "GetDetails")]
public IEnumerable<PersonBase> Get()
{
return new List<PersonBase>()
{
new Student("Student A", "Student No. 01"),
new Employ("Employee 01", "Baidu")
};
}
}
API response:
[
{
"name": "Student A"
},
{
"name": "Employee 01"
}
]
Notice the problem? The extended properties of the Student and Employ instances (Number of Student, CompanyName of Employ) are not serialized. How can we serialize all properties of the derived classes?
2. Implementing Serialization of All Properties of the Derived Classes
Referencing the Microsoft documentation "How to serialize properties of derived classes with System.Text.Json", there are two relatively simple approaches.
2.1. Approach for .NET 7 and earlier
Before .NET 7, System.Text.Json did not support serialization of polymorphic type hierarchies. For example, if the return type of an endpoint is an interface or an abstract class collection, only the properties defined by the interface or abstract class are serialized, even if the runtime type has additional properties.
Solution: Change the endpoint return type from IEnumerable<PersonBase> to object, and change the List<PersonBase> implementation to List<object>:
[HttpGet(Name = "GetDetails")]
public object Get()
{
return new List<object>()
{
new Student("Student A", "Student No. 01"),
new Employ("Employee 01", "Baidu")
};
}
After the change, the endpoint successfully returns detailed JSON information:
[
{
"number": "Student No. 01",
"name": "Student A"
},
{
"companyName": "Baidu",
"name": "Employee 01"
}
]
Principle: After changing to object, serialization defaults to serializing the concrete implementation classes. Before the change, System.Text.Json only recognized the parent class.
2.2. Approach for .NET 7 and later
Starting from .NET 7, System.Text.Json supports serialization and deserialization of polymorphic type hierarchies using attribute annotations.
We restore the endpoint to its original signature and add attributes to the abstract class to specify which derived types should be mapped during base class serialization:
[JsonDerivedType(typeof(Student))]
[JsonDerivedType(typeof(Employ))]
public abstract class PersonBase
Problem solved, the endpoint returns the same JSON as above.
The documentation describes JsonDerivedTypeAttribute: When placed on a type declaration, it indicates that the specified derived type should be selected for polymorphic serialization. It also exposes functionality for specifying type discriminators.
3. Summary
Choose the approach based on the .NET version. The second approach requires you to know the derived types explicitly. For detailed usage, see the Microsoft documentation: How to serialize properties of derived classes with System.Text.Json
If you have a better approach, feel free to leave a comment.
- WeChat Technical Exchange Group: Add WeChat (codewf) with note "Join Group"
- QQ Technical Exchange Group: 771992300.
