I try to migrate from Newtonsoft.Json to System.Text.Json. I want to deserialize abstract class. Newtonsoft.Json has TypeNameHandling for this. Is there any way to deserialize abstract class via System.Text.Json on .net core 3.0?

推荐答案

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

答案是是and不是,这取决于你所说的"possible"是什么意思.

no个多态反序列化(相当于Newtonsoft.Json的TypeNameHandling)支持built-inSystem.Text.Json个.这是因为读取JSON有效负载内指定为字符串的.NET类型名称(如$typeMETADATA属性)来创建对象是not recommended,因为它引入了潜在的安全问题(有关更多信息,请参见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>来添加对多态反序列化的支持,所以从这个意义上说,这是可能的.

The docs show an example of how to do that using a type discriminator property: https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization

Let's look at an example.

Say you have a base class and a couple of derived 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; }
}

You can create the following JsonConverter<BaseClass> that writes the type discriminator while serializing and reads it to figure out which type to deserialize. You can register that converter on the 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();
    }
}

This is what serialization and deserialization would look like (including comparison with 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());
}

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

Json相关问答推荐

如何在使用GO时检测JSON中不需要的字段?

与错误相关的未定义&Quot;不是有效的JSON

如何创建生成两个不同对象的JSON数组的SQL查询?

在深度嵌套数组中使用布尔属性的jq-select

使用 Powershell,如何将 Azure AD 组成员转换为 Json 对象(文件),然后可以更新?

报告重复的对象键

VSCode 为 python 文件添加标尺,但不为 C 文件添加标尺.为什么?

Android 如何判断小时时间是否在两个时间之间?

从 oracle 数据库中的 json blob 打印值

如何 Select 一个值,这是可选的 - 使用 jq

Kotlin Android Room 处理 Moshi TypeConverter 中的空对象列表

使用 Ansible 解析来自 Juniper 交换机的 JSON 输出

hook到 Decodable.init() 以获得未指定的键?

Json.NET SerializeObject 转义值以防止 XSS

如何对使用转换器的 Grails 服务进行单元测试?

从 Postgres 中的 json 对象中提取键、值

使用 ajax 将 JSON 发送到 PHP

在 Webpack 中加载静态 JSON 文件

我可以使用空字符串作为对象标识符吗?

JSON JQ 如果没有 else