using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
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) == typeToConvert;
ref Utf8JsonReader reader, Type objectType, JsonSerializerOptions options)
if (!JsonDocument.TryParseValue(ref reader, out JsonDocument? doc))
throw new JsonException();
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 Exception($"Unknown type: {typeName}");
if (type.IsGenericTypeDefinition)
type = type.MakeGenericType(objectType.GetGenericArguments());
if (doc.Deserialize(type, options) is not T result)
throw new Exception($"Could not deserialize {typeof(T)}");
public override void Write(
Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
throw new Exception($"Can't serialize null {typeof(T)}");
JsonElement jEl = JsonSerializer.SerializeToElement(value, value.GetType(), options);
JsonObject jObj = JsonObject.Create(jEl) ?? throw new JsonException();
jObj[discriminatorPropName] = getDiscriminator(value.GetType());
jObj.WriteTo(writer, options);
public int Base1 { get; set; }
public int Foo1 { get; set; }
public int Bar1 { get; set; }
public class WithBaseProp
public required Base BaseProp { get; set; }
public static void Main()
string basePropJson = """
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($"{nameof(foo)}'s JSON: {JsonSerializer.Serialize<Base>(foo, options)}");
Console.WriteLine($"{nameof(bar)}'s JSON: {JsonSerializer.Serialize<Base>(bar, options)}");
Console.WriteLine($"{nameof(baseProp)}'s JSON: {JsonSerializer.Serialize<WithBaseProp>(baseProp, options)}");