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

Is polymorphic deserialization possible in System.Text.Json?

我尝试从 Newtonsoft.Json 迁移到 System.Text.Json。 我想反序列化摘要 class。 Newtonsoft.Json 对此有 TypeNameHandling。 有什么方法可以在 .net core 3.0 上通过 System.Text.Json 反序列化抽象 class?

请试试我写的这个库作为 System.Text.Json 的扩展以提供多态性: https://github.com/dahomey-technologies/Dahomey.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; }
}

Inherited 类 必须手动注册到鉴别器约定注册表,以便让框架了解鉴别器值和类型之间的映射:

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

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

结果:

{
  "$type": "Tests.WeatherForecastDerived, Tests",
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
  "WindSpeed": 35
}

Is polymorphic deserialization possible in System.Text.Json?

答案是不是,取决于你的意思 "possible".

没有多态反序列化(相当于Newtonsoft.Json的TypeNameHandling)支持内置System.Text.Json。这是因为读取 JSON 有效负载中指定为字符串的 .NET 类型名称(例如 $type 元数据 属性)来创建对象 不推荐 因为它引入了潜在的安全问题(有关详细信息,请参阅 https://github.com/dotnet/corefx/issues/41347#issuecomment-535779492)。

Allowing the payload to specify its own type information is a common source of vulnerabilities in web applications.

但是,一种通过创建 JsonConverter<T> 添加您自己的多态反序列化支持的方法,所以从这个意义上说,这是可能的。

文档显示了如何使用 类型鉴别器 属性 执行此操作的示例: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization

让我们看一个例子。

假设你有一个基础 class 和几个派生的 classes:

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; }
}

您可以创建以下 JsonConverter<BaseClass> 在序列化时写入类型鉴别器并读取它以确定要反序列化的类型。您可以在 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();
    }
}

这是序列化和反序列化的样子(包括与 Newtonsoft.Json 的比较):

private static void PolymorphicSupportComparison()
{
    var objects = new List<BaseClass> { new DerivedA(), new DerivedB() };

    // Using: System.Text.Json
    var options = new JsonSerializerOptions
    {
        Converters = { new BaseClassConverter() },
        WriteIndented = true
    };

    string jsonString = JsonSerializer.Serialize(objects, options);
    Console.WriteLine(jsonString);
    /*
     [
      {
        "TypeDiscriminator": 1,
        "TypeValue": {
            "Str": null,
            "Int": 0
        }
      },
      {
        "TypeDiscriminator": 2,
        "TypeValue": {
            "Bool": false,
            "Int": 0
        }
      }
     ]
    */

    var roundTrip = JsonSerializer.Deserialize<List<BaseClass>>(jsonString, options);


    // Using: Newtonsoft.Json
    var settings = new Newtonsoft.Json.JsonSerializerSettings
    {
        TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects,
        Formatting = Newtonsoft.Json.Formatting.Indented
    };

    jsonString = Newtonsoft.Json.JsonConvert.SerializeObject(objects, settings);
    Console.WriteLine(jsonString);
    /*
     [
      {
        "$type": "PolymorphicSerialization.DerivedA, PolymorphicSerialization",
        "Str": null,
        "Int": 0
      },
      {
        "$type": "PolymorphicSerialization.DerivedB, PolymorphicSerialization",
        "Bool": false,
        "Int": 0
      }
     ]
    */

    var originalList = JsonConvert.DeserializeObject<List<BaseClass>>(jsonString, settings);

    Debug.Assert(originalList[0].GetType() == roundTrip[0].GetType());
}

这是另一个 Whosebug 问题,展示了如何使用接口(而不是抽象 classes)支持多态反序列化,但类似的解决方案适用于任何多态:

我最终得到了那个解决方案。它很轻巧,对我来说足够通用。

类型鉴别器转换器

public class TypeDiscriminatorConverter<T> : JsonConverter<T> where T : ITypeDiscriminator
{
    private readonly IEnumerable<Type> _types;

    public TypeDiscriminatorConverter()
    {
        var type = typeof(T);
        _types = AppDomain.CurrentDomain.GetAssemblies()
            .SelectMany(s => s.GetTypes())
            .Where(p => type.IsAssignableFrom(p) && p.IsClass && !p.IsAbstract)
            .ToList();
    }

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

        using (var jsonDocument = JsonDocument.ParseValue(ref reader))
        {
            if (!jsonDocument.RootElement.TryGetProperty(nameof(ITypeDiscriminator.TypeDiscriminator), out var typeProperty))
            {
                throw new JsonException();
            }

            var type = _types.FirstOrDefault(x => x.Name == typeProperty.GetString());
            if (type == null)
            {
                throw new JsonException();
            }

            var jsonObject = jsonDocument.RootElement.GetRawText();
            var result = (T) JsonSerializer.Deserialize(jsonObject, type, options);

            return result;
        }
    }

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

界面

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);
}

如果你 class 包含 baseClass 属性 那么你将他反序列化为 baseClass。 如果你的 baseClass 是抽象的并且包含 baseClass 属性 那么你会得到 Exception.

这样写比较稳妥:

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();
    }
}

但您的 BaseClass 不必包含 属性 类型为 BaseClass 或继承者。

这就是我的所有抽象类型的 JsonConverter:

        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);
        }

我真的很喜欢 Demetrius 的回答,但我认为您可以在可重用性方面走得更远。我提出了以下解决方案:

JsonConverterFactory:

/// <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;
    }

}

JsonConverter:

/// <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));
            });

抛出这个选项:使用源代码生成器为带有 属性 标记特殊属性的对象自动生成 JsonConverter

你可以尝试使用这个包,但它需要 .net5

https://github.com/wivuu/Wivuu.JsonPolymorphism

生成器查看标有鉴别器属性的 属性 的类型,然后查找从持有鉴别器的类型继承的类型以匹配枚举的每个案例

此处来源:https://github.com/wivuu/Wivuu.JsonPolymorphism/blob/master/Wivuu.JsonPolymorphism/JsonConverterGenerator.cs

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");

对于接口 属性 反序列化,我创建了一个简单的 StaticTypeMapConverter

    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);

我根据 的回答更改了一些内容。

我个人喜欢这种方式,因为客户端可以直接将他们的对象提供给服务器。 但是,'Type' 属性 必须是对象中的第一个。

基础 class 和派生 classes:

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<IBaseClass> 在序列化时读取和检查 'Type' 属性。它将使用它来确定要反序列化的类型。 reader 必须被复制,因为我们读取第一个 属性 作为类型。然后我们必须再次读取完整的对象(将其传递给 Deserialize 方法)。

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();
        }
    }
}

客户端现在可以按如下方式发送对象:

// DerivedA
{
    "Type": 0,
    "Str": "Hello world!"
}

// DerivedB
{
    "Type": 1,
    "Bool": false
}

编辑:

编辑了 Read 方法,以便能够处理 属性 名称不在第一位的情况。现在它只是读取 json 并停止,直到找到 'Type' 属性 name

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

        while (typeReader.Read())
        {
            if (typeReader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            string propertyName = typeReader.GetString();

            if (propertyName.Equals(nameof(IBaseClass.Type)))
            {
                break;
            }

            typeReader.Skip();
        }

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

        IGraphOptions baseClass = default;
        GraphType type = (GraphType)typeReader.GetInt32();
        ....
        // The switch..
        ....


老实说,我认为这个自定义 System.Text JsonConverter 的设置方式不必要地复杂,我更喜欢 Newtonsoft JsonConverter。

我想引入另一个适用于分层、安全、双向、通用用法的实现。

以下注意事项

  • 这是一个性能和内存“噩梦”,但对于大多数场景来说已经足够好了(原因:因为您需要提前阅读 $type 然后需要返回 reader)。
  • 它仅在多态基础是抽象的/从不序列化为实例本身时才有效(原因:因为否则常规转换器无法在派生的 类 上工作,因为它会进入堆栈溢出)。
  • 在 .NET 6 下工作...不会在 3.1 中工作。

例子

public abstract record QueryClause(); // the abstract is kind of important
public record AndClause(QueryClause[] SubClauses) : QueryClause();
public record OrClause(QueryClause[] SubClauses) : QueryClause();

// ...

JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new BaseClassConverter<QueryClause>(
                    typeof(AndClause),
                    typeof(OrClause)));

// ...

转换器

public class BaseClassConverter<TBaseType> : JsonConverter<TBaseType>
    where TBaseType : class
{
    private readonly Type[] _types;
    private const string TypeProperty = "$type";

    public BaseClassConverter(params Type[] types)
    {
        _types = types;
    }

    public override bool CanConvert(Type type)
        => typeof(TBaseType) == type; // only responsible for the abstract base

    public override TBaseType Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options)
    {
        TBaseType result;

        if (JsonDocument.TryParseValue(ref reader, out var doc))
        {
            if (doc.RootElement.TryGetProperty(TypeProperty, out var typeProperty))
            {
                var typeName = typeProperty.GetString();
                var type = _types.FirstOrDefault(t => t.Name == typeName) ?? throw new JsonException($"{TypeProperty} specifies an invalid type");

                var rootElement = doc.RootElement.GetRawText();

                result = JsonSerializer.Deserialize(rootElement, type, options) as TBaseType ?? throw new JsonException("target type could not be serialized");
            }
            else
            {
                throw new JsonException($"{TypeProperty} missing");
            }
        }
        else
        {
            throw new JsonException("Failed to parse JsonDocument");
        }

        return result;
    }

    public override void Write(
        Utf8JsonWriter writer,
        TBaseType value,
        JsonSerializerOptions options)
    {
        var type = value.GetType();

        if (_types.Any(t => type.Name == t.Name))
        {
            var jsonElement = JsonSerializer.SerializeToElement(value, type, options);

            var jsonObject = JsonObject.Create(jsonElement) ?? throw new JsonException();
            jsonObject[TypeProperty] = type.Name;

            jsonObject.WriteTo(writer, options);
        }
        else
        {
            throw new JsonException($"{type.Name} with matching base type {typeof(TBaseType).Name} is not registered.");
        }
    }
}

如果你发现了什么,请给我留言。

1 的一些荣誉。

基于已接受的答案,但使用 KnownTypeAttribute 来发现类型(通常枚举所有类型会导致不需要的类型加载异常),并在转换器中添加鉴别器 属性 而不是 class 自己实现:

public class TypeDiscriminatorConverter<T> : JsonConverter<T> 
{
    private readonly IEnumerable<Type> _types;

    public TypeDiscriminatorConverter()
    {
        var type = typeof(T);
        var knownTypes = type.GetCustomAttributes(typeof(KnownTypeAttribute), false).OfType<KnownTypeAttribute>();
        _types = knownTypes.Select(x => x.Type).ToArray();
    }

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

        using (var jsonDocument = JsonDocument.ParseValue(ref reader))
        {
            if (!jsonDocument.RootElement.TryGetProperty("discriminator",
                out var typeProperty))
            {
                throw new JsonException();
            }

            var type = _types.FirstOrDefault(x => x.FullName == typeProperty.GetString());
            if (type == null)
            {
                throw new JsonException();
            }

            var jsonObject = jsonDocument.RootElement.GetRawText();
            var result = (T)JsonSerializer.Deserialize(jsonObject, type, options);

            return result;
        }
    }

    public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();
        using (JsonDocument document = JsonDocument.Parse(JsonSerializer.Serialize(value)))
        {
            writer.WritePropertyName("discriminator");
            writer.WriteStringValue(value.GetType().FullName);
            foreach (var property in document.RootElement.EnumerateObject())
            {
                property.WriteTo(writer);
            }

        }
        writer.WriteEndObject();
    }
}

你可以这样使用:

[JsonConverter(typeof(JsonInheritanceConverter))]
[KnownType(typeof(DerivedA))]
[KnownType(typeof(DerivedB))]
public abstract class BaseClass
{ 
    //..
}

不是很优雅或高效,但可以快速编写少量子类型的代码:

List<Dictionary<string, object>> generics = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(json);
List<InputOutputInstanceDto> result = new List<ParentType>();
foreach (Dictionary<string, object> item in generics)
{
    switch(item["dataType"]) // use whatever field is in your parent/interface
    {
        case "Type1":
            result.Add(JsonSerializer.Deserialize<Type1>(
                            JsonSerializer.Serialize(item)));
            break
        // add cases for each child type supported
        default:
            result.Add(JsonSerializer.Deserialize<ParentType>(
                            JsonSerializer.Serialize(item)));
            break;
    }
}