Overview

Availability: .NET 7

System.Text.Json supports polymorphic type hierarchies with these attributes:

  • [JsonDerivedType] — indicates that the specified subtype should be opted into polymorphic serialization
  • [JsonPolymorphic] — indicates that the type should be serialized polymorphically

Serializing Properties of a Derived Class

Consider this base class…

[JsonDerivedType(typeof(WeatherForecastWithCity))]
public class WeatherForecastBase
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

…and this derived class:

public class WeatherForecastWithCity : WeatherForecastBase
{
    public string? City { get; set; }
}

Assume the type argument of Serialize<T> at compile time is WeatherForecastBase:

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);

The City property is serialized because the weatherForecastBase object is actually a WeatherForecastWithCity object since we used the [JsonDerivedType] attribute:

{
  "City": "Milwaukee",
  "Date": "2022-09-26T00:00:00-05:00",
  "TemperatureCelsius": 15,
  "Summary": "Cool"
}

Deserializing Properties of a Derived Class

To enable polymorphic deserialization, a type discriminator must be specified for the derived class:

[JsonDerivedType(typeof(WeatherForecastBase), typeDiscriminator: "base")]
[JsonDerivedType(typeof(WeatherForecastWithCity), typeDiscriminator: "withCity")]
public class WeatherForecastBase
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

public class WeatherForecastWithCity : WeatherForecastBase
{
    public string? City { get; set; }
}

This added metadata allows the serializer to serialize and deserialize the payload as WeatherForecastWithCity from its base WeatherForecastBase type:

WeatherForecastBase weather = new WeatherForecastWithCity
{
    City = "Milwaukee",
    Date = new DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
    TemperatureCelsius = 15,
    Summary = "Cool"
}
var json = JsonSerializer.Serialize<WeatherForecastBase>(weather, options);
Console.WriteLine(json);

Output:

{
  "$type" : "withCity", // this is the type discriminator metadata 
  "City": "Milwaukee",
  "Date": "2022-09-26T00:00:00-05:00",
  "TemperatureCelsius": 15,
  "Summary": "Cool"
}

JsonPolymorphic

The [JsonPolymorphic] attribute customizes the type discriminator’s name. The default name is $type:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$discriminator")]
[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: "3d")]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public sealed class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

BasePoint point = new ThreeDimensionalPoint { X = 1, Y = 2, Z = 3 };
var json = JsonSerializer.Serialize<BasePoint>(point);
Console.WriteLine(json);
// Sample output:
//  { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }