web-dev-qa-db-ja.com

NET Core 3.1でSystem.Text.Jsonを使用してオブジェクト内のオブジェクトの値を取得する方法

。Net Core 3.1 Webアプリケーションを作成し、リクエストされたモデルが次のようになるリクエストを投稿しました。

public class RequestPayload
    {
        public string MessageName { get; set; }

        public object Payload { get; set; }
    }

私はコア3.1に非常に慣れておらず、ペイロードプロパティの値を取得するのに苦労しています。これについて誰かが私を助けてくれますか?

解決策を見つけている間、私はNewtonsoftSystem.Text.Jsonを比較してErrorも取得しました。

Newtonsoft私はableを使用して、以下に示すモデルをシリアライズおよびデシリアライズします、

public class RequestPayload
    {
        public string MessageName { get; set; }

        public object Payload { get; set; }

        //Problem is here -> TYPE
        public Type PayloadType { get; set; }
    }

しかしSystem.Text.Jsonを使用していますnot While serializingがエラーを取得しました "System.Text.Json.JsonException: '可能なオブジェクトサイクルはサポートされていないことが検出されました。」

deserializationをテストするために、何らかの方法でJSONを作成し、System.Text.Jsonを使用してそれを非直列化しようとしましたが、エラー「System.Text.Json.JsonException:」が発生しました。 」

使用System.Text.Json.JsonSerializer、それは問題ですか、またはこれを機能させる他の可能性がありますか?

1
Darshana

私はコア3.1に非常に慣れておらず、ペイロードプロパティの値を取得するのに苦労しています。これについて誰かが私を助けてくれますか?

System.Objectプロパティの場合、unlikeNewtonsoft.JsonSystem.Text.Jsonしないを推測しようとしますプリミティブ値のJSONペイロードのtypetrue12345.67"hello"など)。同様に、オブジェクトや配列などの複雑なJSON値({"Name":"hi"}[1, 2, 3]など)の場合、オブジェクトプロパティは、渡されたJSONを表すボックス化されたJsonElementとして設定されます。これは、Newtonsoft.JsonJObjectobject propertyに格納して複雑な型を作成する方法と似ています。 https://docs.Microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1 を参照してください

Newtonsoft.JsonのJObjectの場合と同様に、JsonElementを使用してJSONドキュメントオブジェクトモデル(DOM)内の値をトラバースおよびアクセスし、そのAPIを呼び出して.NET値( GetProperty(String)GetInt32()など)。

次の例は、JSONをPayloadに逆シリアル化した後、RequestPayload値にアクセスする方法を示しています。

private static void ObjectPropertyExample()
{
    using JsonDocument doc = JsonDocument.Parse("{\"Name\":\"Darshana\"}");
    JsonElement payload = doc.RootElement.Clone();

    var requestPayload = new RequestPayload
    {
        MessageName = "message",
        Payload = payload
    };

    string json = JsonSerializer.Serialize(requestPayload);
    Console.WriteLine(json);
    // {"MessageName":"message","Payload":{"Name":"Darshana"}}

    RequestPayload roundtrip = JsonSerializer.Deserialize<RequestPayload>(json);

    JsonElement element = (JsonElement)roundtrip.Payload;
    string name = element.GetProperty("Name").GetString();
    Assert.Equal("Darshana", name);
}

解決策を見つけるときに、NewtonsoftとSystem.Text.Jsonを比較してエラーが発生しました。

System.Typeプロパティを含むクラスをシリアル化することは問題ありませんが、特に推奨されていません、特にWebアプリケーションの場合(ただし、情報開示に潜在的な問題があります) 。

一方、deserializationJSONはTypeプロパティを含むクラスに、特にType.GetType(untrusted-string-input)を使用すると、は間違いなく推奨アプリケーションに潜在的なセキュリティの脆弱性をもたらすため。

これが、組み込みのSystem.Text.Jsonが意図的にシリアル化/非シリアル化Typeプロパティをサポートしない理由です。シリアル化中に表示される例外メッセージは、Typeがオブジェクトグラフ内にサイクルを含み、JsonSerializerが現在サイクルを処理していないためです。クラスをJSONにシリアル化(つまり、書き込み)するだけの場合は、独自のJsonConverter<Type>を作成してサポートを追加できます(Newtonsoft.Jsonと同じJSONを生成するため)。次のようなものが機能します。

private class CustomJsonConverterForType : JsonConverter<Type>
{
    public override Type Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        // Caution: Deserialization of type instances like this 
        // is not recommended and should be avoided
        // since it can lead to potential security issues.

        // If you really want this supported (for instance if the JSON input is trusted):
        // string assemblyQualifiedName = reader.GetString();
        // return Type.GetType(assemblyQualifiedName);
        throw new NotSupportedException();
    }

    public override void Write(Utf8JsonWriter writer, Type value,
        JsonSerializerOptions options)
    {
        // Use this with caution, since you are disclosing type information.
        writer.WriteStringValue(value.AssemblyQualifiedName);
    }
}

次に、カスタムコンバーターをオプションに追加し、それをJsonSerializer.Serializeに渡します。

var options = new JsonSerializerOptions();
options.Converters.Add(new CustomJsonConverterForType());

re-evaluatingなぜ最初にシリアル化および逆シリアル化されているクラスのTypeプロパティが必要なのかを検討してください。

Typeプロパティを含むクラスをType.GetType(string)を使用してデシリアライズしない理由の詳細とコンテキストについては、 https://github.com/dotnet/corefx/issues/42712 を参照してください。 。

カスタムコンバーターの作成方法の詳細は次のとおりです。 https://docs.Microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to

より安全に機能する方法(したがって、私がお勧めすること)は、予期してサポートし、明示的に作成する静的に既知の型のリストを含む型判別子列挙を使用することですJsonConverter<Type>内の列挙値に基づくタイプ。

これがどのように見えるかの例です:

// Let's assume these are the list of types we expect for the `Type` property
public class ExpectedType1 { }
public class ExpectedType2 { }
public class ExpectedType3 { }

public class CustomJsonConverterForType : JsonConverter<Type>
{
    public override Type Read(ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();

        Type type = typeDiscriminator switch
        {
            TypeDiscriminator.ExpectedType1 => typeof(ExpectedType1),
            TypeDiscriminator.ExpectedType2 => typeof(ExpectedType2),
            TypeDiscriminator.ExpectedType3 => typeof(ExpectedType3),
            _ => throw new NotSupportedException(),
        };
        return type;
    }

    public override void Write(Utf8JsonWriter writer, Type value,
        JsonSerializerOptions options)
    {
        if (value == typeof(ExpectedType1))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType1);
        }
        else if (value == typeof(ExpectedType2))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType2);
        }
        else if (value == typeof(ExpectedType3))
        {
            writer.WriteNumberValue((int)TypeDiscriminator.ExpectedType3);
        }
        else
        {
            throw new NotSupportedException();
        }
    }

    // Used to map supported types to an integer and vice versa.
    private enum TypeDiscriminator
    {
        ExpectedType1 = 1,
        ExpectedType2 = 2,
        ExpectedType3 = 3,
    }
}

private static void TypeConverterExample()
{
    var requestPayload = new RequestPayload
    {
        MessageName = "message",
        Payload = "payload",
        PayloadType = typeof(ExpectedType1)
    };

    var options = new JsonSerializerOptions()
    {
        Converters = { new CustomJsonConverterForType() }
    };

    string json = JsonSerializer.Serialize(requestPayload, options);
    Console.WriteLine(json);
    // {"MessageName":"message","Payload":"payload","PayloadType":1}

    RequestPayload roundtrip = JsonSerializer.Deserialize<RequestPayload>(json, options);
    Assert.Equal(typeof(ExpectedType1), roundtrip.PayloadType);
}
6
ahsonkhan