如何在.net core 3.1中使用System.Text.Json获取对象内部对象的值

How to get value of object inside object using System.Text.Json in .net core 3.1

创建了一个 .Net Core 3.1 Web 应用程序并发布请求,其中请求的模型看起来像,

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

        public object Payload { get; set; }
    }

我是 core 3.1 的新手,正在努力获取 Payload 的值 属性,谁能帮我解决这个问题?

在找到解决方案的同时,我还比较了 NewtonsoftSystem.Text.Json 并得到 Error.

使用 Newtonsoft能够 序列化和反序列化如下所示的模型,

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不是 虽然 序列化 出现错误 "System.Text.Json.JsonException: 'A possible object cycle was detected which is not supported."

为了测试 反序列化 ,以某种方式创建了 JSON 并尝试使用 System.Text.Json 对其进行反序列化,但出现错误 "System.Text.Json.JsonException: 'The JSON value could not be converted to System.Type. "

使用了 System.Text.Json.JsonSerializer,这是一个问题还是有其他可能使它起作用?

I am very new to core 3.1 and struggling to get the value of Payload property, Can anyone help me on this?

对于System.Object属性,不像Newtonsoft.JsonSystem.Text.Json不会尝试推断JSON 原始值有效载荷的 type(例如 true12345.67"hello")。同样,对于复杂的 JSON 值,如对象和数组(例如 {"Name":"hi"}[1, 2, 3]),对象 属性 被设置为表示传入 JSON。这类似于 Newtonsoft.JsonJObject 存储到复杂类型的 object property 中。 参见 https://docs.microsoft.com/en-us/dotnet/api/system.text.json.jsonelement?view=netcore-3.1

就像使用 Newtonsoft.Json 的 JObject 一样,您可以使用 JsonElement 并对其调用转换 API 以获取 .NET 值(例如 GetProperty(String)GetInt32())。

以下示例显示了如何访问 Payload 值,一旦您将 JSON 反序列化为 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);
}

While finding the solution I also compared Newtonsoft and System.Text.Json and got Error.

尽管序列化包含 System.Type 属性 的 class 是可行的,但 不推荐 ,尤其是对于网络应用程序(虽然存在信息泄露的潜在问题)。

另一方面,反序列化 JSON 到包含 Type 属性 的 class,尤其是使用 Type.GetType(untrusted-string-input) 绝对不推荐,因为它会在您的应用程序中引入潜在的安全漏洞。

这就是内置System.Text.Json故意不支持serializing/deserializingType属性的原因。您在序列化时看到的异常消息是因为 Type 在其对象图中包含一个循环,而 JsonSerializer 当前不处理循环。如果您只关心将 class 序列化(即写入)到 JSON,您可以创建自己的 JsonConverter<Type> 来添加对它的支持(产生与 JSON 相同的 JSON =15=] 会)。像下面这样的东西会起作用:

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

考虑重新评估 为什么您需要 Type 属性 在您的 class 上开始序列化和反序列化。

有关为什么不应使用 Type.GetType(string).

反序列化包含 Type 属性的 class 的更多信息和上下文,请参阅 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);
}