C# System.Text.Json中是否可以进行多态反序列化?

C# System.Text.Json中是否可以进行多态反序列化?,c#,json,.net-core-3.0,system.text.json,C#,Json,.net Core 3.0,System.text.json,我尝试从Newtonsoft.Json迁移到System.Text.Json。 我想反序列化抽象类。Json对此具有TypeNameHandling。 有没有办法通过.net core 3.0上的System.Text.Json反序列化抽象类?请尝试我编写的这个库,作为System.Text.Json的扩展,以提供多态性: 如果引用实例的实际类型与声明的类型不同,则鉴别器属性将自动添加到输出json: public class WeatherForecast { public Date

我尝试从Newtonsoft.Json迁移到System.Text.Json。 我想反序列化抽象类。Json对此具有TypeNameHandling。
有没有办法通过.net core 3.0上的System.Text.Json反序列化抽象类?

请尝试我编写的这个库,作为System.Text.Json的扩展,以提供多态性:

如果引用实例的实际类型与声明的类型不同,则鉴别器属性将自动添加到输出json:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

public class WeatherForecastDerived : WeatherForecast
{
    public int WindSpeed { get; set; }
}
继承的类必须手动注册到鉴别器约定注册表,以便让框架知道鉴别器值和类型之间的映射:

JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry();
registry.RegisterType<WeatherForecastDerived>();

string json = JsonSerializer.Serialize<WeatherForecast>(weatherForecastDerived, options);

请尝试我编写的这个库作为System.Text.Json的扩展,以提供多态性:

如果引用实例的实际类型与声明的类型不同,则鉴别器属性将自动添加到输出json:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

public class WeatherForecastDerived : WeatherForecast
{
    public int WindSpeed { get; set; }
}
继承的类必须手动注册到鉴别器约定注册表,以便让框架知道鉴别器值和类型之间的映射:

JsonSerializerOptions options = new JsonSerializerOptions();
options.SetupExtensions();
DiscriminatorConventionRegistry registry = options.GetDiscriminatorConventionRegistry();
registry.RegisterType<WeatherForecastDerived>();

string json = JsonSerializer.Serialize<WeatherForecast>(weatherForecastDerived, options);
System.Text.Json中是否可以进行多态反序列化

答案是肯定的和否定的,这取决于你所说的“可能”是什么意思

没有多态反序列化(相当于Newtonsoft.Json的
typenameholling
)支持内置的
System.Text.Json
。这是因为不建议读取JSON负载中指定为字符串的.NET类型名称(例如
$type
元数据属性)来创建对象,因为这会带来潜在的安全问题(有关更多信息,请参阅)

允许负载指定自己的类型信息是web应用程序中常见的漏洞来源

但是,有一种方法可以通过创建一个
JsonConverter
来添加您自己对多态反序列化的支持,因此从这个意义上说,这是可能的

文档显示了如何使用类型鉴别器属性执行此操作的示例:

让我们看一个例子

假设您有一个基类和两个派生类:

public class BaseClass
{
    public int Int { get; set; }
}
public class DerivedA : BaseClass
{
    public string Str { get; set; }
}
public class DerivedB : BaseClass
{
    public bool Bool { get; set; }
}
public interface IBaseClass
{
    public DerivedType Type { get; set; }
}
public class DerivedA : IBaseClass
{
    public DerivedType Type => DerivedType.DerivedA;
    public string Str { get; set; }
}
public class DerivedB : IBaseClass
{
    public DerivedType Type => DerivedType.DerivedB;
    public bool Bool { get; set; }
}

private enum DerivedType
{
    DerivedA = 0,
    DerivedB = 1
}
您可以创建以下
JsonConverter
,它在序列化时写入类型鉴别器,然后读取它以确定要反序列化的类型。您可以在
JsonSerializerOptions
上注册该转换器

public class BaseClassConverter : JsonConverter<BaseClass>
{
    private enum TypeDiscriminator
    {
        BaseClass = 0,
        DerivedA = 1,
        DerivedB = 2
    }

    public override bool CanConvert(Type type)
    {
        return typeof(BaseClass).IsAssignableFrom(type);
    }

    public override BaseClass Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        }

        if (!reader.Read()
                || reader.TokenType != JsonTokenType.PropertyName
                || reader.GetString() != "TypeDiscriminator")
        {
            throw new JsonException();
        }

        if (!reader.Read() || reader.TokenType != JsonTokenType.Number)
        {
            throw new JsonException();
        }

        BaseClass baseClass;
        TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();
        switch (typeDiscriminator)
        {
            case TypeDiscriminator.DerivedA:
                if (!reader.Read() || reader.GetString() != "TypeValue")
                {
                    throw new JsonException();
                }
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }
                baseClass = (DerivedA)JsonSerializer.Deserialize(ref reader, typeof(DerivedA));
                break;
            case TypeDiscriminator.DerivedB:
                if (!reader.Read() || reader.GetString() != "TypeValue")
                {
                    throw new JsonException();
                }
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }
                baseClass = (DerivedB)JsonSerializer.Deserialize(ref reader, typeof(DerivedB));
                break;
            default:
                throw new NotSupportedException();
        }

        if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject)
        {
            throw new JsonException();
        }

        return baseClass;
    }

    public override void Write(
        Utf8JsonWriter writer,
        BaseClass value,
        JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        if (value is DerivedA derivedA)
        {
            writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedA);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, derivedA);
        }
        else if (value is DerivedB derivedB)
        {
            writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedB);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, derivedB);
        }
        else
        {
            throw new NotSupportedException();
        }

        writer.WriteEndObject();
    }
}
公共类BaseClassConverter:JsonConverter
{
私有枚举类型鉴别器
{
基类=0,
德里维达=1,
DerivedB=2
}
公共覆盖布尔CanConvert(类型)
{
返回类型(基类)。IsAssignableFrom(类型);
}
公共重写基类读取(
参考Utf8JsonReader读取器,
类型转换,
JsonSerializerOptions(可选)
{
if(reader.TokenType!=JsonTokenType.StartObject)
{
抛出新的JsonException();
}
如果(!reader.Read()
||reader.TokenType!=JsonTokenType.PropertyName
||reader.GetString()!=“TypeDiscriminator”)
{
抛出新的JsonException();
}
如果(!reader.Read()| | reader.TokenType!=JsonTokenType.Number)
{
抛出新的JsonException();
}
基类基类;
TypeDiscriminator TypeDiscriminator=(TypeDiscriminator)reader.GetInt32();
开关(类型鉴别器)
{
case TypeDiscriminator.DerivedA:
如果(!reader.Read()| | reader.GetString()!=“TypeValue”)
{
抛出新的JsonException();
}
如果(!reader.Read()| | reader.TokenType!=JsonTokenType.StartObject)
{
抛出新的JsonException();
}
baseClass=(DerivedA)JsonSerializer.反序列化(ref reader,typeof(DerivedA));
打破
case TypeDiscriminator.DerivedB:
如果(!reader.Read()| | reader.GetString()!=“TypeValue”)
{
抛出新的JsonException();
}
如果(!reader.Read()| | reader.TokenType!=JsonTokenType.StartObject)
{
抛出新的JsonException();
}
baseClass=(DerivedB)JsonSerializer.Deserialize(ref reader,typeof(DerivedB));
打破
违约:
抛出新的NotSupportedException();
}
如果(!reader.Read()| | reader.TokenType!=JsonTokenType.EndObject)
{
抛出新的JsonException();
}
返回基类;
}
公共覆盖无效写入(
Utf8JsonWriter,
基类值,
JsonSerializerOptions(可选)
{
writer.WriteStartObject();
如果(值为DerivedA DerivedA)
{
writer.WriteEnumber(“TypeDiscriminator”,(int)TypeDiscriminator.DerivedA);
writer.WritePropertyName(“TypeValue”);
JsonSerializer.Serialize(writer,derivedA);
}
else if(值为DerivedB DerivedB)
{
writer.WriteEnumber(“TypeDiscriminator”,(int)TypeDiscriminator.DerivedB);
writer.WritePropertyName(“TypeValue”);
JsonSerializer.Serialize(writer,derivedB);
}
其他的
{
抛出新的NotSupportedException();
}
writer.WriteEndObject();
}
}
这就是序列化和反序列化的样子(包括与Newtonsoft.Json的比较):

private static void多态支持比较()
{
var objects=新列表{new DerivedA(),new DerivedB()};
//使用:System.Text.Json
var options=新的JsonSerializerOptions
{
转换器={new BaseClas
public interface ITypeDiscriminator
{
    string TypeDiscriminator { get; }
}
public interface ISurveyStepResult : ITypeDiscriminator
{
    string Id { get; set; }
}

public class BoolStepResult : ISurveyStepResult
{
    public string Id { get; set; }
    public string TypeDiscriminator => nameof(BoolStepResult);

    public bool Value { get; set; }
}

public class TextStepResult : ISurveyStepResult
{
    public string Id { get; set; }
    public string TypeDiscriminator => nameof(TextStepResult);

    public string Value { get; set; }
}

public class StarsStepResult : ISurveyStepResult
{
    public string Id { get; set; }
    public string TypeDiscriminator => nameof(StarsStepResult);

    public int Value { get; set; }
}
public void SerializeAndDeserializeTest()
    {
        var surveyResult = new SurveyResultModel()
        {
            Id = "id",
            SurveyId = "surveyId",
            Steps = new List<ISurveyStepResult>()
            {
                new BoolStepResult(){ Id = "1", Value = true},
                new TextStepResult(){ Id = "2", Value = "some text"},
                new StarsStepResult(){ Id = "3", Value = 5},
            }
        };

        var jsonSerializerOptions = new JsonSerializerOptions()
        {
            Converters = { new TypeDiscriminatorConverter<ISurveyStepResult>()},
            WriteIndented = true
        };
        var result = JsonSerializer.Serialize(surveyResult, jsonSerializerOptions);

        var back = JsonSerializer.Deserialize<SurveyResultModel>(result, jsonSerializerOptions);

        var result2 = JsonSerializer.Serialize(back, jsonSerializerOptions);
        
        Assert.IsTrue(back.Steps.Count == 3 
                      && back.Steps.Any(x => x is BoolStepResult)
                      && back.Steps.Any(x => x is TextStepResult)
                      && back.Steps.Any(x => x is StarsStepResult)
                      );
        Assert.AreEqual(result2, result);
    }
public override bool CanConvert(Type type)
{
    return typeof(BaseClass).IsAssignableFrom(type);
}
public class BaseClass
{
    public int Int { get; set; }
}
public class DerivedA : BaseClass
{
    public string Str { get; set; }
    public BaseClass derived { get; set; }
}
public class DerivedB : BaseClass
{
    public bool Bool { get; set; }
    public BaseClass derived { get; set; }
}



public class BaseClassConverter : JsonConverter<BaseClass>
{
    private enum TypeDiscriminator
    {
        BaseClass = 0,
        DerivedA = 1,
        DerivedB = 2
    }

    public override bool CanConvert(Type type)
    {
        return typeof(BaseClass) == type;
    }

    public override BaseClass Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        }

        if (!reader.Read()
                || reader.TokenType != JsonTokenType.PropertyName
                || reader.GetString() != "TypeDiscriminator")
        {
            throw new JsonException();
        }

        if (!reader.Read() || reader.TokenType != JsonTokenType.Number)
        {
            throw new JsonException();
        }

        BaseClass baseClass;
        TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();
        switch (typeDiscriminator)
        {
            case TypeDiscriminator.DerivedA:
                if (!reader.Read() || reader.GetString() != "TypeValue")
                {
                    throw new JsonException();
                }
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }
                baseClass = (DerivedA)JsonSerializer.Deserialize(ref reader,   typeof(DerivedA), options);
                break;
            case TypeDiscriminator.DerivedB:
                if (!reader.Read() || reader.GetString() != "TypeValue")
                {
                    throw new JsonException();
                }
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }
                baseClass = (DerivedB)JsonSerializer.Deserialize(ref reader,     typeof(DerivedB), options);
                break;
            case TypeDiscriminator.BaseClass:
                if (!reader.Read() || reader.GetString() != "TypeValue")
                {
                    throw new JsonException();
                }
                if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
                {
                    throw new JsonException();
                }
                baseClass = (BaseClass)JsonSerializer.Deserialize(ref reader,     typeof(BaseClass));
                break;
            default:
                throw new NotSupportedException();
        }

        if (!reader.Read() || reader.TokenType != JsonTokenType.EndObject)
        {
            throw new JsonException();
        }

        return baseClass;
    }

    public override void Write(
        Utf8JsonWriter writer,
        BaseClass value,
        JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        if (value is DerivedA derivedA)
        {
            writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedA);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, derivedA, options);
        }
        else if (value is DerivedB derivedB)
        {
            writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.DerivedB);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, derivedB, options);
        }
        else if (value is BaseClass baseClass)
        {
            writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.BaseClass);
            writer.WritePropertyName("TypeValue");
            JsonSerializer.Serialize(writer, baseClass);
        }
        else
        {
            throw new NotSupportedException();
        }

        writer.WriteEndObject();
    }
}
        private class AbstractClassConverter : JsonConverter<object>
        {
            public override object Read(ref Utf8JsonReader reader, Type typeToConvert,
                JsonSerializerOptions options)
            {
                if (reader.TokenType == JsonTokenType.Null) return null;

                if (reader.TokenType != JsonTokenType.StartObject)
                    throw new JsonException("JsonTokenType.StartObject not found.");

                if (!reader.Read() || reader.TokenType != JsonTokenType.PropertyName
                                   || reader.GetString() != "$type")
                    throw new JsonException("Property $type not found.");

                if (!reader.Read() || reader.TokenType != JsonTokenType.String)
                    throw new JsonException("Value at $type is invalid.");

                string assemblyQualifiedName = reader.GetString();

                var type = Type.GetType(assemblyQualifiedName);
                using (var output = new MemoryStream())
                {
                    ReadObject(ref reader, output, options);
                    return JsonSerializer.Deserialize(output.ToArray(), type, options);
                }
            }

            private void ReadObject(ref Utf8JsonReader reader, Stream output, JsonSerializerOptions options)
            {
                using (var writer = new Utf8JsonWriter(output, new JsonWriterOptions
                {
                    Encoder = options.Encoder,
                    Indented = options.WriteIndented
                }))
                {
                    writer.WriteStartObject();
                    var objectIntend = 0;

                    while (reader.Read())
                    {
                        switch (reader.TokenType)
                        {
                            case JsonTokenType.None:
                            case JsonTokenType.Null:
                                writer.WriteNullValue();
                                break;
                            case JsonTokenType.StartObject:
                                writer.WriteStartObject();
                                objectIntend++;
                                break;
                            case JsonTokenType.EndObject:
                                writer.WriteEndObject();
                                if(objectIntend == 0)
                                {
                                    writer.Flush();
                                    return;
                                }
                                objectIntend--;
                                break;
                            case JsonTokenType.StartArray:
                                writer.WriteStartArray();
                                break;
                            case JsonTokenType.EndArray:
                                writer.WriteEndArray();
                                break;
                            case JsonTokenType.PropertyName:
                                writer.WritePropertyName(reader.GetString());
                                break;
                            case JsonTokenType.Comment:
                                writer.WriteCommentValue(reader.GetComment());
                                break;
                            case JsonTokenType.String:
                                writer.WriteStringValue(reader.GetString());
                                break;
                            case JsonTokenType.Number:
                                writer.WriteNumberValue(reader.GetInt32());
                                break;
                            case JsonTokenType.True:
                            case JsonTokenType.False:
                                writer.WriteBooleanValue(reader.GetBoolean());
                                break;
                            default:
                                throw new ArgumentOutOfRangeException();
                        }
                    }
                }
            }

            public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
            {
                writer.WriteStartObject();
                var valueType = value.GetType();
                var valueAssemblyName = valueType.Assembly.GetName();
                writer.WriteString("$type", $"{valueType.FullName}, {valueAssemblyName.Name}");

                var json = JsonSerializer.Serialize(value, value.GetType(), options);
                using (var document = JsonDocument.Parse(json, new JsonDocumentOptions
                {
                    AllowTrailingCommas = options.AllowTrailingCommas,
                    MaxDepth = options.MaxDepth
                }))
                {
                    foreach (var jsonProperty in document.RootElement.EnumerateObject())
                        jsonProperty.WriteTo(writer);
                }

                writer.WriteEndObject();
            }

            public override bool CanConvert(Type typeToConvert) => 
                typeToConvert.IsAbstract && !EnumerableInterfaceType.IsAssignableFrom(typeToConvert);
        }
/// <summary>
/// Represents the <see cref="JsonConverterFactory"/> used to create <see cref="AbstractClassConverter{T}"/>
/// </summary>
public class AbstractClassConverterFactory
    : JsonConverterFactory
{

    /// <summary>
    /// Gets a <see cref="Dictionary{TKey, TValue}"/> containing the mappings of types to their respective <see cref="JsonConverter"/>
    /// </summary>
    protected static Dictionary<Type, JsonConverter> Converters = new Dictionary<Type, JsonConverter>();

    /// <summary>
    /// Initializes a new <see cref="AbstractClassConverterFactory"/>
    /// </summary>
    /// <param name="namingPolicy">The current <see cref="JsonNamingPolicy"/></param>
    public AbstractClassConverterFactory(JsonNamingPolicy namingPolicy)
    {
        this.NamingPolicy = namingPolicy;
    }

    /// <summary>
    /// Gets the current <see cref="JsonNamingPolicy"/>
    /// </summary>
    protected JsonNamingPolicy NamingPolicy { get; }

    /// <inheritdoc/>
    public override bool CanConvert(Type typeToConvert)
    {
        return typeToConvert.IsClass && typeToConvert.IsAbstract && typeToConvert.IsDefined(typeof(DiscriminatorAttribute));
    }

    /// <inheritdoc/>
    public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
    {
        if(!Converters.TryGetValue(typeToConvert, out JsonConverter converter))
        {
            Type converterType = typeof(AbstractClassConverter<>).MakeGenericType(typeToConvert);
            converter = (JsonConverter)Activator.CreateInstance(converterType, this.NamingPolicy);
            Converters.Add(typeToConvert, converter);
        }
        return converter;
    }

}
/// <summary>
/// Represents the <see cref="JsonConverter"/> used to convert to/from an abstract class
/// </summary>
/// <typeparam name="T">The type of the abstract class to convert to/from</typeparam>
public class AbstractClassConverter<T>
    : JsonConverter<T>
{

    /// <summary>
    /// Initializes a new <see cref="AbstractClassConverter{T}"/>
    /// </summary>
    /// <param name="namingPolicy">The current <see cref="JsonNamingPolicy"/></param>
    public AbstractClassConverter(JsonNamingPolicy namingPolicy)
    {
        this.NamingPolicy = namingPolicy;
        DiscriminatorAttribute discriminatorAttribute = typeof(T).GetCustomAttribute<DiscriminatorAttribute>();
        if (discriminatorAttribute == null)
            throw new NullReferenceException($"Failed to find the required '{nameof(DiscriminatorAttribute)}'");
        this.DiscriminatorProperty = typeof(T).GetProperty(discriminatorAttribute.Property, BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance);
        if (this.DiscriminatorProperty == null)
            throw new NullReferenceException($"Failed to find the specified discriminator property '{discriminatorAttribute.Property}' in type '{typeof(T).Name}'");
        this.TypeMappings = new Dictionary<string, Type>();
        foreach (Type derivedType in TypeCacheUtil.FindFilteredTypes($"nposm:json-polymorph:{typeof(T).Name}", 
            (t) => t.IsClass && !t.IsAbstract && t.BaseType == typeof(T)))
        {
            DiscriminatorValueAttribute discriminatorValueAttribute = derivedType.GetCustomAttribute<DiscriminatorValueAttribute>();
            if (discriminatorValueAttribute == null)
                continue;
            string discriminatorValue = null;
            if (discriminatorValueAttribute.Value.GetType().IsEnum)
                discriminatorValue = EnumHelper.Stringify(discriminatorValueAttribute.Value, this.DiscriminatorProperty.PropertyType);
            else
                discriminatorValue = discriminatorValueAttribute.Value.ToString();
            this.TypeMappings.Add(discriminatorValue, derivedType);
        }
    }

    /// <summary>
    /// Gets the current <see cref="JsonNamingPolicy"/>
    /// </summary>
    protected JsonNamingPolicy NamingPolicy { get; }

    /// <summary>
    /// Gets the discriminator <see cref="PropertyInfo"/> of the abstract type to convert
    /// </summary>
    protected PropertyInfo DiscriminatorProperty { get; }

    /// <summary>
    /// Gets an <see cref="Dictionary{TKey, TValue}"/> containing the mappings of the converted type's derived types
    /// </summary>
    protected Dictionary<string, Type> TypeMappings { get; }

    /// <inheritdoc/>
    public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartObject)
            throw new JsonException("Start object token type expected");
        using (JsonDocument jsonDocument = JsonDocument.ParseValue(ref reader))
        {
            string discriminatorPropertyName = this.NamingPolicy?.ConvertName(this.DiscriminatorProperty.Name);
            if (!jsonDocument.RootElement.TryGetProperty(discriminatorPropertyName, out JsonElement discriminatorProperty))
                throw new JsonException($"Failed to find the required '{this.DiscriminatorProperty.Name}' discriminator property");
            string discriminatorValue = discriminatorProperty.GetString();
            if (!this.TypeMappings.TryGetValue(discriminatorValue, out Type derivedType))
                throw new JsonException($"Failed to find the derived type with the specified discriminator value '{discriminatorValue}'");
            string json = jsonDocument.RootElement.GetRawText();
            return (T)JsonSerializer.Deserialize(json, derivedType);
        }
    }

    /// <inheritdoc/>
    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, (object)value, options);
    }

}
/// <summary>
/// Represents the <see cref="Attribute"/> used to indicate the property used to discriminate derived types of the marked class
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DiscriminatorAttribute
    : Attribute
{

    /// <summary>
    /// Initializes a new <see cref="DiscriminatorAttribute"/>
    /// </summary>
    /// <param name="property">The name of the property used to discriminate derived types of the class marked by the <see cref="DiscriminatorAttribute"/></param>
    public DiscriminatorAttribute(string property)
    {
        this.Property = property;
    }

    /// <summary>
    /// Gets the name of the property used to discriminate derived types of the class marked by the <see cref="DiscriminatorAttribute"/>
    /// </summary>
    public string Property { get; }

}
 /// <summary>
/// Represents the <see cref="Attribute"/> used to indicate the discriminator value of a derived type
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DiscriminatorValueAttribute
    : Attribute
{

    /// <summary>
    /// Initializes a new <see cref="DiscriminatorValueAttribute"/>
    /// </summary>
    /// <param name="value">The value used to discriminate the derived type marked by the <see cref="DiscriminatorValueAttribute"/></param>
    public DiscriminatorValueAttribute(object value)
    {
        this.Value = value;
    }

    /// <summary>
    /// Gets the value used to discriminate the derived type marked by the <see cref="DiscriminatorValueAttribute"/>
    /// </summary>
    public object Value { get; }

}
[Discriminator(nameof(Type))]
public abstract class Identity
{

    public virtual IdentityType Type { get; protected set; }

}

[DiscriminatorValue(IdentityType.Person)]
public class Person
   : Identity
{

}
 this.Services.AddControllersWithViews()
            .AddJsonOptions(options => 
            {
                options.JsonSerializerOptions.Converters.Add(new AbstractClassConverterFactory(options.JsonSerializerOptions.PropertyNamingPolicy));
            });
enum AnimalType
{
    Insect,
    Mammal,
    Reptile,
    Bird // <- This causes an easy to understand build error if it's missing a corresponding inherited type!
}

// My base type is 'Animal'
abstract partial record Animal( [JsonDiscriminator] AnimalType type, string Name );

// Animals with type = 'Insect' will automatically deserialize as `Insect`
record Insect(int NumLegs = 6, int NumEyes=4) : Animal(AnimalType.Insect, "Insectoid");

record Mammal(int NumNipples = 2) : Animal(AnimalType.Mammal, "Mammalian");

record Reptile(bool ColdBlooded = true) : Animal(AnimalType.Reptile, "Reptilian");
    public class StaticTypeMapConverter<SourceType, TargetType> : JsonConverter<SourceType> 
        where SourceType : class
        where TargetType : class, new()
    {

        public override SourceType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            using (var jsonDocument = JsonDocument.ParseValue(ref reader))
            {
                var jsonObject = jsonDocument.RootElement.GetRawText();
                var result = (SourceType)JsonSerializer.Deserialize(jsonObject, typeof(TargetType), options);

                return result;
            }
        }

        public override void Write(Utf8JsonWriter writer, SourceType value, JsonSerializerOptions options)
        {
            JsonSerializer.Serialize(writer, (object)value, options);
        }
    }
                var jsonSerializerOptions = new JsonSerializerOptions()
                {
                    Converters = { 
                        new StaticTypeMapConverter<IMyInterface, MyImplementation>(),
                        new StaticTypeMapConverter<IMyInterface2, MyInterface2Class>(),
                    },
                    WriteIndented = true
                };

                var config = JsonSerializer.Deserialize<Config>(configContentJson, jsonSerializerOptions);
public interface IBaseClass
{
    public DerivedType Type { get; set; }
}
public class DerivedA : IBaseClass
{
    public DerivedType Type => DerivedType.DerivedA;
    public string Str { get; set; }
}
public class DerivedB : IBaseClass
{
    public DerivedType Type => DerivedType.DerivedB;
    public bool Bool { get; set; }
}

private enum DerivedType
{
    DerivedA = 0,
    DerivedB = 1
}
public class BaseClassConverter : JsonConverter<IBaseClass>
{
    public override IBaseClass Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        // Creating a copy of the reader (The derived deserialisation has to be done from the start)
        Utf8JsonReader typeReader = reader;

        if (reader.TokenType != JsonTokenType.StartObject)
        {
            throw new JsonException();
        }

        if (!reader.Read() || reader.TokenType != JsonTokenType.PropertyName)
        {
            throw new JsonException();
        }
        
        if (!reader.Read() || reader.TokenType != JsonTokenType.Number)
        {
            throw new JsonException();
        }

        IBaseClass baseClass = default;
        DerivedType type= (DerivedType)reader.GetInt32();

        switch (type)
        {
            case DerivedType.DerivedA:
                baseClass = (DerivedA)JsonSerializer.Deserialize(ref reader, typeof(DerivedA));
                break;
            case DerivedType.DerivedB:
                baseClass = (DerivedB)JsonSerializer.Deserialize(ref reader, typeof(DerivedB));
                break;
            default:
                throw new NotSupportedException();
        }

        return baseClass;
    }

    public override void Write(
        Utf8JsonWriter writer,
        IBaseClass value,
        JsonSerializerOptions options) 
    {
        switch(value)
        {
            case DerivedA derivedA:
                JsonSerializer.Serialize(writer, derivedA, options);
                break;
            case DerivedB derivedB:
                JsonSerializer.Serialize(writer, derivedB, options);
                break;
            default:
                throw new NotSupportedException();
        }
    }
}