System.Text.Json でポリモーフィックなデシリアライゼーションは可能ですか? 質問する

System.Text.Json でポリモーフィックなデシリアライゼーションは可能ですか? 質問する

Newtonsoft.Json から System.Text.Json に移行しようとしています。抽象クラスを逆シリアル化したいのですが、Newtonsoft.Json にはこのための TypeNameHandling があります。.net core 3.0 で System.Text.Json を介して抽象クラスを逆シリアル化する方法はありますか?

ベストアンサー1

System.Text.Json でポリモーフィックなデシリアライゼーションは可能ですか?

答えはイエスですそしていいえ、あなたが何を意味しているかによって異なります"可能"

があるいいえポリモーフィックデシリアライゼーション(Newtonsoft.Json と同等TypeNameHandling)のサポート内蔵System.Text.Json。これは、JSONペイロード内の文字列として指定された.NET型名($typeメタデータプロパティなど)を読み取ってオブジェクトを作成すると、推奨されません潜在的なセキュリティ上の懸念が生じるため(https://github.com/dotnet/corefx/issues/41347#issuecomment-535779492詳細についてはこちらをご覧ください。

ペイロードが独自の型情報を指定できるようにすると、Web アプリケーションで脆弱性が発生する一般的な原因となります。

しかし、を作成することで、ポリモーフィックなデシリアライゼーションに対する独自のサポートを追加する方法があるJsonConverter<T>ため、その意味では可能です。

ドキュメントでは、型判別子財産:https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization

例を見てみましょう。

基本クラスといくつかの派生クラスがあるとします。

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

以下に、抽象クラスではなくインターフェースを使用してポリモーフィックなデシリアライゼーションをサポートする方法を示す別の StackOverflow の質問を示しますが、同様のソリューションはあらゆるポリモーフィズムに適用されます。System.Text.Json のカスタム コンバーターで子オブジェクトを手動でシリアル化/逆シリアル化する簡単な方法はありますか?

おすすめ記事