using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
public class Base { public int Base1 { get; set; } }
public class Foo : Base { public int Foo1 { get; set; } }
public class Bar : Base { public int Bar1 { get; set; } }
public class WithBaseProp { public required Base BaseProp { get; set; } }
public static void Main()
string fooJson = """{ "Base1": 1, "Foo1": 2, "type": "Foo" }""";
string barJson = """{ "Base1": 3, "Bar1": 4, "type": "Bar" }""";
string basePropJson = """{ "BaseProp": { "Base1": 5, "Foo1": 6, "type": "Foo" } }""";
Type[] types = new[] { typeof(Foo), typeof(Bar) };
JsonSerializerOptions options = new()
Converters = { new PolymorphicJsonConverter<Base>("type", t => t.Name, types) }
Base foo = JsonSerializer.Deserialize<Base>(fooJson, options)!;
Base bar = JsonSerializer.Deserialize<Base>(barJson, options)!;
WithBaseProp baseProp = JsonSerializer.Deserialize<WithBaseProp>(basePropJson, options)!;
Console.WriteLine($"{nameof(foo)}'s type: {foo.GetType()}");
Console.WriteLine($"{nameof(bar)}'s type: {bar.GetType()}");
Console.WriteLine($"{nameof(baseProp)}'s property type: {baseProp.BaseProp.GetType()}");
Console.WriteLine("When serialized as Base:");
Console.WriteLine($"{nameof(foo)}'s JSON: {JsonSerializer.Serialize<Base>(foo, options)}");
Console.WriteLine($"{nameof(bar)}'s JSON: {JsonSerializer.Serialize<Base>(bar, options)}");
Console.WriteLine("When serialized as subtype:");
Console.WriteLine($"{nameof(foo)}'s JSON: {JsonSerializer.Serialize(foo, options)}");
Console.WriteLine($"{nameof(bar)}'s JSON: {JsonSerializer.Serialize(bar, options)}");
Console.WriteLine($"{nameof(baseProp)}'s JSON: {JsonSerializer.Serialize<WithBaseProp>(baseProp, options)}");
sealed public class PolymorphicJsonConverter<T> : JsonConverter<T>
private readonly string discriminatorPropName;
private readonly Func<Type, string> getDiscriminator;
private readonly IReadOnlyDictionary<string, Type> discriminatorToSubtype;
public PolymorphicJsonConverter(
string typeDiscriminatorPropertyName,
Func<Type, string> getDiscriminatorForSubtype,
IEnumerable<Type> subtypes)
discriminatorPropName = typeDiscriminatorPropertyName;
getDiscriminator = getDiscriminatorForSubtype;
discriminatorToSubtype = subtypes.ToDictionary(getDiscriminator, t => t);
public override bool CanConvert(Type typeToConvert)
=> typeof(T).IsAssignableFrom(typeToConvert);
JsonSerializerOptions? originalOptions = null;
JsonSerializerOptions? optionsWithoutConverters = null;
JsonTypeInfo getTypeInfo(Type t, JsonSerializerOptions givenOpts)
if (optionsWithoutConverters is null)
originalOptions = givenOpts;
optionsWithoutConverters = new(givenOpts);
optionsWithoutConverters.Converters.Clear();
if (originalOptions != givenOpts)
$"A {typeof(PolymorphicJsonConverter<>).Name} instance cannot " +
$"be used in multiple {nameof(JsonSerializerOptions)} instances!");
return optionsWithoutConverters.GetTypeInfo(t);
ref Utf8JsonReader reader, Type objectType, JsonSerializerOptions options)
using var doc = JsonDocument.ParseValue(ref reader);
JsonElement root = doc.RootElement;
JsonElement typeField = root.GetProperty(discriminatorPropName);
if (typeField.GetString() is not string typeName)
$"Could not find string property {discriminatorPropName} " +
$"when trying to deserialize {typeof(T).Name}");
if (!discriminatorToSubtype.TryGetValue(typeName, out Type? type))
throw new JsonException($"Unknown type: {typeName}");
JsonTypeInfo info = getTypeInfo(type, options);
T instance = (T)info.CreateObject!();
foreach (var p in info.Properties)
if (p.Set is null) continue;
if (!root.TryGetProperty(p.Name, out JsonElement propValue))
throw new JsonException($"Required property {p.Name} was not found.");
p.Set(instance, propValue.Deserialize(p.PropertyType, options));
public override void Write(
Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
Type type = value!.GetType();
throw new NotSupportedException(
$"Cannot serialize an instance of type {typeof(T)}, only its subtypes.");
writer.WriteStartObject();
writer.WriteString(discriminatorPropName, getDiscriminator(type));
JsonTypeInfo info = getTypeInfo(type, options);
foreach (var p in info.Properties)
if (p.Get is null) continue;
writer.WritePropertyName(p.Name);
object? pVal = p.Get(value);
JsonSerializer.Serialize(writer, pVal, options);