【问题标题】:System.Text.Json.JsonElement ToObject workaroundSystem.Text.Json.JsonElement ToObject 解决方法
【发布时间】:2019-09-27 17:03:58
【问题描述】:

我想知道 System.Text.Json 的 Json.NET 中 ToObject<>() 方法的等效项。

使用 Json.NET,您可以使用任何 JToken 并将其转换为类。例如:

var str = ""; // some json string
var jObj = JObject.Parse(str);
var myClass = jObj["SomeProperty"].ToObject<SomeClass>();

我们如何使用 .NET Core 3 的新 System.Text.Json 来做到这一点

var str = ""; // some json string
var jDoc = JsonDocument.Parse(str);
var myClass = jDoc.RootElement.GetProperty("SomeProperty"). <-- now what??

最初我想我只是将在jDoc.RootElement.GetPRoperty("SomeProperty") 中返回的JsonElement 转换为字符串,然后反序列化该字符串。但我觉得这可能不是最有效的方法,而且我真的找不到其他方法的文档。

【问题讨论】:

    标签: c# .net-core-3.0 system.text.json


    【解决方案1】:

    我遇到了同样的问题,所以我写了一些现在可以正常工作的扩展方法。如果他们将其作为内置提供以避免额外分配给字符串,那就太好了。

    public static T ToObject<T>(this JsonElement element)
    {
        var json = element.GetRawText();
        return JsonSerializer.Deserialize<T>(json);
    }
    public static T ToObject<T>(this JsonDocument document)
    {
        var json = document.RootElement.GetRawText();
        return JsonSerializer.Deserialize<T>(json);
    }
    

    然后按如下方式使用:

    jDoc.RootElement.GetProperty("SomeProperty").ToObject<SomeClass>();
    

    【讨论】:

    • 这正是我最终所做的。我发布了这个问题,看看是否有更有效的方法没有你提到的字符串分配。也许我们得等到 system.text.json 成熟一点。
    • 是的,尽管它不仅仅与字符串分配有关。它更多的是再次解析整个 JSON 的文本表示。在创建 JsonElement/Document 时已经解析过一次,所以也很浪费 CPU。
    • 老兄...正是我想要做的。非常感谢您添加此内容!
    【解决方案2】:

    在 .NET 6 中 扩展方法被添加到 JsonSerializer 以直接从 JsonElementJsonDocument 反序列化对象:

    public static partial class JsonSerializer
    {
        public static TValue? Deserialize<TValue>(this JsonDocument document, JsonSerializerOptions? options = null);
        public static object? Deserialize(this JsonDocument document, Type returnType, JsonSerializerOptions? options = null);
        public static TValue? Deserialize<TValue>(this JsonDocument document, JsonTypeInfo<TValue> jsonTypeInfo);
        public static object? Deserialize(this JsonDocument document, Type returnType, JsonSerializerContext context);
    
        public static TValue? Deserialize<TValue>(this JsonElement element, JsonSerializerOptions? options = null);
        public static object? Deserialize(this JsonElement element, Type returnType, JsonSerializerOptions? options = null);
        public static TValue? Deserialize<TValue>(this JsonElement element, JsonTypeInfo<TValue> jsonTypeInfo);
        public static object? Deserialize(this JsonElement element, Type returnType, JsonSerializerContext context);
    }
    

    现在你可以做:

    using var jDoc = JsonDocument.Parse(str);
    var myClass = jDoc.RootElement.GetProperty("SomeProperty").Deserialize<SomeClass>();
    

    注意事项:

    • JsonDocument 是一次性的。根据docs 的说法,这个类利用了池化内存中的资源......未能正确处理这个对象将导致内存没有返回到池中,这将增加对各个部分的 GC 影响框架。

      因此,请务必使用 using 声明声明您的 jDoc

    • 新方法应该存在于.NET 6.0 Preview RC1

      它们是为了响应增强请求We should be able serialize and serialize from DOM #31274而添加的,该请求已关闭。

    在 .NET 5 及更早版本中不存在这些方法。作为一种解决方法,您可以通过写入中间byte 缓冲区而不是字符串来获得更好的性能,因为JsonDocumentUtf8JsonReader 都直接使用byte 跨度而不是字符串或char 跨度。如docs中所述:

    序列化为 UTF-8 比使用基于字符串的方法快 5-10%。区别在于字节(如 UTF-8)不需要转换为字符串(UTF-16)。

    public static partial class JsonExtensions
    {
        public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
        {
            var bufferWriter = new ArrayBufferWriter<byte>();
            using (var writer = new Utf8JsonWriter(bufferWriter))
                element.WriteTo(writer);
            return JsonSerializer.Deserialize<T>(bufferWriter.WrittenSpan, options);
        }
    
        public static T ToObject<T>(this JsonDocument document, JsonSerializerOptions options = null)
        {
            if (document == null)
                throw new ArgumentNullException(nameof(document));
            return document.RootElement.ToObject<T>(options);
        }       
    }
    

    演示小提琴here.

    【讨论】:

    • 大约 18 个月后仍在 dotnet 5 上使用此答案。在element.WriteTo(writer); 之后需要writer.Flush();,否则你有时会得到System.Text.Json.JsonException: 'Expected depth to be zero at the end of the JSON payload. There is an open JSON object or array that should be closed.
    • @mclayton - 我确实通过 using 语句处理了 Utf8JsonWriter writer,并且根据文档,dispose method 在 IBufferWriter 的情况下,这会推进底层的 @987654351 @ 基于迄今为止所写的内容。 并且flush method根据迄今为止所写的内容推进底层的IBufferWriter&lt;T&gt; 两者似乎都没有必要。
    • 啊,我的错。我正在“使用” C# 8.0 形式的 using ... using var writer = new Utf8JsonWriter(bufferWriter); 所以 Dispose 只在函数末尾被调用,这完全改变了代码的行为,需要 Flush 来修复它。几个括号有什么区别!
    • @Ason - JsonSerializer 在内部从 utf8 字节序列反序列化,因此 JsonSerializer.Deserialize&lt;TValue&gt;(string json, ...) 在反序列化之前转码回 utf8。我的方法只是来回跳过转码。
    • ArrayBufferWriter 似乎是一个 .NET Core 的东西。在 .NET Framework 4.8 中工作时,我可以用 Microsoft.Toolkit.HighPerformance nuget 包中的 ArrayPoolBufferWriter 替换它吗?
    【解决方案3】:

    dbc's answer 相同,只是包括允许您通过Type returnType 指定返回类型的方法。

    public static partial class JsonExtensions
    {
        public static T ToObject<T>(this JsonElement element, JsonSerializerOptions options = null)
        {
            var bufferWriter = new ArrayBufferWriter<byte>();
            using (var writer = new Utf8JsonWriter(bufferWriter))
            {
                element.WriteTo(writer);
            }
    
            return JsonSerializer.Deserialize<T>(bufferWriter.WrittenSpan, options);
        }
    
        public static T ToObject<T>(this JsonDocument document, JsonSerializerOptions options = null)
        {
            if (document == null)
            {
                throw new ArgumentNullException(nameof(document));
            }
    
            return document.RootElement.ToObject<T>(options);
        }       
    
        public static object ToObject(this JsonElement element, Type returnType, JsonSerializerOptions options = null)
        {
            var bufferWriter = new ArrayBufferWriter<byte>();
            using (var writer = new Utf8JsonWriter(bufferWriter))
            {
                element.WriteTo(writer);
            }
    
            return JsonSerializer.Deserialize(bufferWriter.WrittenSpan, returnType, options);
        }
    
        public static object ToObject(this JsonDocument document, Type returnType, JsonSerializerOptions options = null)
        {
            if (document == null)
            {
                throw new ArgumentNullException(nameof(document));
            }
    
            return document.RootElement.ToObject(returnType, options);
        }       
    }
    

    【讨论】:

    • 序列化回文本似乎不是一种有效的方法
    【解决方案4】:

    .NET 6 引入了 System.Text.Json.Nodes 命名空间,它提供了一种使用与 Json.Net 几乎完全相同的语法来执行此操作的方法:

    var str = ""; // some json string
    var node = JsonNode.Parse(str);
    var myClass = node["SomeProperty"].Deserialize<SomeClass>();
    

    命名空间包括 4 种新类型:JsonNodeJsonArrayJsonObjectJsonValue,可用于访问或修改 DOM 中的值。 JsonNode 是其他三种类型的基类。

    dbc's answer 中列出的Deserialize 扩展方法也已添加到对JsonNode 的操作,例如:

    public static TValue? Deserialize<TValue>(this JsonNode? node, JsonSerializerOptions? options = null);
    

    JsonNode 不是一次性的,因此您不需要使用using 语法。

    分别使用AsObject()AsArray() 解析为JsonObjectJsonArray

    // parse array
    JsonArray arr = JsonNode.Parse(@"[{""Name"": ""Bob"", ""Age"":30}]").AsArray();
    // parse object
    JsonObject obj = JsonNode.Parse(@"{""Name"": ""Bob"", ""Age"":30}").AsObject();
    // get a value
    var date = JsonNode.Parse(@"{""Date"":""2021-12-21T13:24:46+04:00""}")["Date"].GetValue<DateTimeOffset>();
    

    解析 json 后,就可以导航、过滤和转换 DOM 和/或应用 Deserialize&lt;T&gt;() 以映射到您的具体类型。

    要序列化回 json 字符串,您可以使用ToJsonString(),例如:

    string innerNodeJson = node["SomeProperty"].ToJsonString();
    

    有关JsonObject 的更多详细信息,请参阅Equivalent of JObject in System.Text.Json 的回复。

    【讨论】:

      猜你喜欢
      • 2018-06-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-13
      • 2021-12-29
      • 2015-07-10
      • 2012-10-11
      • 1970-01-01
      • 2016-03-09
      相关资源
      最近更新 更多