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

Dar*_*ana 3 c# json.net .net-core-3.1 system.text.json

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

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

        public object Payload { get; set; }
    }
Run Code Online (Sandbox Code Playgroud)

我对核心 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; }
    }
Run Code Online (Sandbox Code Playgroud)

但是使用System.Text.Json不是序列化时出现错误“System.Text.Json.JsonException:'检测到不支持的可能的对象循环。”

为了测试反序列化,以某种方式创建了 JSON 并尝试使用 System.Text.Json 对其进行反序列化,但收到错误“System.Text.Json.JsonException:'JSON 值无法转换为 System.Type。”

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

ahs*_*han 12

我对核心 3.1 非常陌生,正在努力获得 Payload 属性的价值,有人可以帮我吗?

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

就像使用 Newtonsoft.Json 的 一样JObject,您可以使用JsonElement和调用转换 API来遍历和访问 JSON 文档对象模型 (DOM) 中的值以获取 .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);
}
Run Code Online (Sandbox Code Playgroud)

在找到解决方案的同时,我还比较了 Newtonsoft 和 System.Text.Json 并得到了错误。

尽管序列化包含System.Type属性的类是可以的,但建议这样做,尤其是对于 Web 应用程序(尽管存在信息泄露的潜在问题)。

在另一方面,反序列化JSON成一个包含类Type物业,尤其是在使用Type.GetType(untrusted-string-input)绝对不推荐,因为它在应用程序中引入了潜在的安全漏洞。

这就是为什么内置函数System.Text.Json故意不支持序列化/反序列化Type属性。您在序列化时看到的异常消息是因为Type在其对象图中包含一个循环,并且当前JsonSerializer不处理循环。如果您只关心将类序列化(即写入)到 JSON,您可以创建自己的类JsonConverter<Type>来添加对它的支持(生成相同的 JSON Newtonsoft.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以将自定义转换器添加到选项中并将其传递给JsonSerializer.Serialize

var options = new JsonSerializerOptions();
options.Converters.Add(new CustomJsonConverterForType());
Run Code Online (Sandbox Code Playgroud)

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

https://github.com/dotnet/corefx/issues/42712以获取更多信息和背景周围为什么你不应该反序列化包含类Type使用性质Type.GetType(string)

以下是有关如何编写自定义转换器的更多信息: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);
}
Run Code Online (Sandbox Code Playgroud)