如何在 System.Text.Json 中使用 Utf8JsonReader

您所在的位置:网站首页 eclipse如何使用json 如何在 System.Text.Json 中使用 Utf8JsonReader

如何在 System.Text.Json 中使用 Utf8JsonReader

2023-06-05 07:28| 来源: 网络整理| 查看: 265

如何在 System.Text.Json 中使用 Utf8JsonReader 项目 06/01/2023

本文介绍如何使用 Utf8JsonReader 类型生成自定义分析程序和反序列化程序。

Utf8JsonReader 是面向 UTF-8 编码 JSON 文本的一个高性能、低分配的只进读取器,从 ReadOnlySpan 或 ReadOnlySequence 读取信息。 Utf8JsonReader 是一种低级类型,可用于生成自定义分析器和反序列化程序。 JsonSerializer.Deserialize 方法在后台使用 Utf8JsonReader。

不能直接从 Visual Basic 代码使用 Utf8JsonReader。 有关详细信息,请参阅 Visual Basic 支持。

下面的示例演示如何使用 Utf8JsonReader 类:

var options = new JsonReaderOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }; var reader = new Utf8JsonReader(jsonUtf8Bytes, options); while (reader.Read()) { Console.Write(reader.TokenType); switch (reader.TokenType) { case JsonTokenType.PropertyName: case JsonTokenType.String: { string? text = reader.GetString(); Console.Write(" "); Console.Write(text); break; } case JsonTokenType.Number: { int intValue = reader.GetInt32(); Console.Write(" "); Console.Write(intValue); break; } // Other token types elided for brevity } Console.WriteLine(); } ' This code example doesn't apply to Visual Basic. For more information, go to the following URL: ' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support

前面的代码假设 jsonUtf8 变量是包含有效 JSON(编码为 UTF-8)的字节数组。

使用 Utf8JsonReader 筛选数据

下面的示例演示如何同步读取文件并搜索值。

using System.Text; using System.Text.Json; namespace SystemTextJsonSamples { public class Utf8ReaderFromFile { private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name"); private static ReadOnlySpan Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF }; public static void Run() { // ReadAllBytes if the file encoding is UTF-8: string fileName = "UniversitiesUtf8.json"; ReadOnlySpan jsonReadOnlySpan = File.ReadAllBytes(fileName); // Read past the UTF-8 BOM bytes if a BOM exists. if (jsonReadOnlySpan.StartsWith(Utf8Bom)) { jsonReadOnlySpan = jsonReadOnlySpan.Slice(Utf8Bom.Length); } // Or read as UTF-16 and transcode to UTF-8 to convert to a ReadOnlySpan //string fileName = "Universities.json"; //string jsonString = File.ReadAllText(fileName); //ReadOnlySpan jsonReadOnlySpan = Encoding.UTF8.GetBytes(jsonString); int count = 0; int total = 0; var reader = new Utf8JsonReader(jsonReadOnlySpan); while (reader.Read()) { JsonTokenType tokenType = reader.TokenType; switch (tokenType) { case JsonTokenType.StartObject: total++; break; case JsonTokenType.PropertyName: if (reader.ValueTextEquals(s_nameUtf8)) { // Assume valid JSON, known schema reader.Read(); if (reader.GetString()!.EndsWith("University")) { count++; } } break; } } Console.WriteLine($"{count} out of {total} have names that end with 'University'"); } } } ' This code example doesn't apply to Visual Basic. For more information, go to the following URL: ' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support

若要获取此示例的异步版本,请参阅 .NET 示例 JSON 项目。

前面的代码:

假设 JSON 包含一个对象数组,并且每个对象都可能包含一个字符串类型的“name”属性。

对对象以及以“University”结尾的属性值进行计数。

假设文件编码为 UTF-16,并将它转码为 UTF-8。 可以使用以下代码,将编码为 UTF-8 的文件直接读入 ReadOnlySpan:

ReadOnlySpan jsonReadOnlySpan = File.ReadAllBytes(fileName);

如果文件包含 UTF-8 字节顺序标记 (BOM),请在将字节传递给 Utf8JsonReader 之前将它删除,因为读取器需要文本。 否则,BOM 被视为无效 JSON,读取器将引发异常。

下面是前面的代码可以读取的 JSON 示例。 生成的摘要消息为“2 out of 4 have names that end with 'University'”:

[ { "web_pages": [ "https://contoso.edu/" ], "alpha_two_code": "US", "state-province": null, "country": "United States", "domains": [ "contoso.edu" ], "name": "Contoso Community College" }, { "web_pages": [ "http://fabrikam.edu/" ], "alpha_two_code": "US", "state-province": null, "country": "United States", "domains": [ "fabrikam.edu" ], "name": "Fabrikam Community College" }, { "web_pages": [ "http://www.contosouniversity.edu/" ], "alpha_two_code": "US", "state-province": null, "country": "United States", "domains": [ "contosouniversity.edu" ], "name": "Contoso University" }, { "web_pages": [ "http://www.fabrikamuniversity.edu/" ], "alpha_two_code": "US", "state-province": null, "country": "United States", "domains": [ "fabrikamuniversity.edu" ], "name": "Fabrikam University" } ] 使用 Utf8JsonReader 从流中读取内容

当读取大型文件(例如,1 GB 或更大的文件)时,你可能希望避免一次性将整个文件加载到内存中。 此时,可以使用 FileStream。

使用 Utf8JsonReader 从流中读取时,适用以下规则:

包含部分 JSON 有效负载的缓冲区必须至少与其中的最大 JSON 令牌一样大,以便读取器可以推进进度。 缓冲区的大小必须至少与 JSON 中的最大空格序列一样大。 读取器不会跟踪已读取的数据,直到它完全读取 JSON 有效负载中的下一个 TokenType。 因此,当缓冲区中有剩余字节时,必须再次将它们传递给读取器。 你可以使用 BytesConsumed 来确定剩余的字节数。

下面的代码演示了如何从流中读取。 本示例显示了 MemoryStream。 类似的代码将使用 FileStream,当 FileStream 在开头包含 UTF-8 BOM 时除外。 在这种情况下,需要先从缓冲区中去除这三个字节,然后再将剩余字节传递到 Utf8JsonReader。 否则,读取器将引发异常,因为 BOM 不被视为 JSON 的有效部分。

示例代码从 4 KB 缓冲区开始,每当发现大小不足以容纳完整的 JSON 令牌(必须容纳完整的令牌,读取器才能推动处理 JSON 有效负载)时,就会使缓冲区大小成倍增加。 仅当设置的初始缓冲区非常小(例如 10 个字节)时,代码片段中提供的 JSON 示例才会触发缓冲区大小增加。 如果将初始缓冲区大小设置为 10,则 Console.WriteLine 语句会说明缓冲区大小增加的原因和影响。 在初始缓冲区大小为 4KB 时,每个 Console.WriteLine 都会显示整个示例 JSON,并且不必增加缓冲区大小。

using System.Text; using System.Text.Json; namespace SystemTextJsonSamples { public class Utf8ReaderPartialRead { public static void Run() { var jsonString = @"{ ""Date"": ""2019-08-01T00:00:00-07:00"", ""Temperature"": 25, ""TemperatureRanges"": { ""Cold"": { ""High"": 20, ""Low"": -10 }, ""Hot"": { ""High"": 60, ""Low"": 20 } }, ""Summary"": ""Hot"", }"; byte[] bytes = Encoding.UTF8.GetBytes(jsonString); var stream = new MemoryStream(bytes); var buffer = new byte[4096]; // Fill the buffer. // For this snippet, we're assuming the stream is open and has data. // If it might be closed or empty, check if the return value is 0. stream.Read(buffer); // We set isFinalBlock to false since we expect more data in a subsequent read from the stream. var reader = new Utf8JsonReader(buffer, isFinalBlock: false, state: default); Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}"); // Search for "Summary" property name while (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals("Summary")) { if (!reader.Read()) { // Not enough of the JSON is in the buffer to complete a read. GetMoreBytesFromStream(stream, buffer, ref reader); } } // Found the "Summary" property name. Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}"); while (!reader.Read()) { // Not enough of the JSON is in the buffer to complete a read. GetMoreBytesFromStream(stream, buffer, ref reader); } // Display value of Summary property, that is, "Hot". Console.WriteLine($"Got property value: {reader.GetString()}"); } private static void GetMoreBytesFromStream( MemoryStream stream, byte[] buffer, ref Utf8JsonReader reader) { int bytesRead; if (reader.BytesConsumed < buffer.Length) { ReadOnlySpan leftover = buffer.AsSpan((int)reader.BytesConsumed); if (leftover.Length == buffer.Length) { Array.Resize(ref buffer, buffer.Length * 2); Console.WriteLine($"Increased buffer size to {buffer.Length}"); } leftover.CopyTo(buffer); bytesRead = stream.Read(buffer.AsSpan(leftover.Length)); } else { bytesRead = stream.Read(buffer); } Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}"); reader = new Utf8JsonReader(buffer, isFinalBlock: bytesRead == 0, reader.CurrentState); } } } ' This code example doesn't apply to Visual Basic. For more information, go to the following URL: ' https://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support

前面的示例未对缓冲区大小的增长设置任何限制。 如果令牌大小太大,则代码可能会失败,并出现 OutOfMemoryException 异常。 如果 JSON 包含大小约为 1 GB 或更大的令牌,则会发生这种情况,因为将 1 GB 大小加倍会导致令牌太大,无法放入 int32 缓冲区。

Utf8JsonReader 是 ref struct

由于 Utf8JsonReader 类型是 ref struct,因此它具有某些限制。 例如,它无法作为字段存储在 ref struct 之外的类或结构中。 若要实现高性能,此类型必须为 ref struct,因为它需要缓存输入 ReadOnlySpan(这本身便是 ref struct)。 此外,此类型是可变的,因为它包含状态。 因此,它按引用传递而不是按值传递。 按值传递会产生结构副本,状态更改会对调用方不可见。 有关如何使用 ref 结构的详细信息,请参阅避免分配。

读取 UTF-8 文本

若要在使用 Utf8JsonReader 时实现可能的最佳性能,请读取已编码为 UTF-8 文本(而不是 UTF-16 字符串)的 JSON 有效负载。 有关代码示例,请参阅使用 Utf8JsonReader 筛选数据。

使用多段 ReadOnlySequence 进行读取

如果 JSON 输入是 ,则在运行读取循环时,可以从读取器上的 ValueSpan 属性访问每个 JSON 元素。 但是,如果输入是 ReadOnlySequence(这是从 PipeReader 读取的结果),则某些 JSON 元素可能会跨 ReadOnlySequence 对象的多个段。 无法在连续内存块中从 ValueSpan 访问这些元素。 而是在每次将多段 ReadOnlySequence 作为输入时,轮询读取器上的 HasValueSequence 属性,以确定如何访问当前 JSON 元素。 下面是推荐模式:

while (reader.Read()) { switch (reader.TokenType) { // ... ReadOnlySpan jsonElement = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; // ... } } 使用 ValueTextEquals 进行属性名称查找

不要使用 ValueSpan 通过对属性名称查找调用 SequenceEqual 来执行逐字节比较。 改为调用 ValueTextEquals,因为该方法会对在 JSON 中转义的任何字符取消转义。 下面的示例演示如何搜索名为“name”的属性:

private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name"); while (reader.Read()) { switch (reader.TokenType) { case JsonTokenType.StartObject: total++; break; case JsonTokenType.PropertyName: if (reader.ValueTextEquals(s_nameUtf8)) { count++; } break; } } 将 null 值读取到可为 null 的值类型中

内置 System.Text.Json API 仅返回不可为 null 的值类型。 例如,Utf8JsonReader.GetBoolean 返回 bool。 如果它在 JSON 中发现 Null,则会引发异常。 下面的示例演示两种用于处理 null 的方法,一种方法是返回可为 null 的值类型,另一种方法是返回默认值:

public bool? ReadAsNullableBoolean() { _reader.Read(); if (_reader.TokenType == JsonTokenType.Null) { return null; } if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False) { throw new JsonException(); } return _reader.GetBoolean(); } public bool ReadAsBoolean(bool defaultValue) { _reader.Read(); if (_reader.TokenType == JsonTokenType.Null) { return defaultValue; } if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False) { throw new JsonException(); } return _reader.GetBoolean(); } 使用解码的 JSON 字符串

从 .NET 7 开始,可以使用 Utf8JsonReader.CopyString 方法而不是 Utf8JsonReader.GetString() 来使用解码的 JSON 字符串。 与始终分配新字符串的 GetString() 不同,CopyString 允许将未转义的字符串复制到你拥有的缓冲区中。 以下代码片段显示了通过 CopyString 使用 UTF-16 字符串的示例。

var reader = new Utf8JsonReader( /* jsonReadOnlySpan */ ); int valueLength = reader.HasValueSequence ? checked((int)reader.ValueSequence.Length) : reader.ValueSpan.Length; char[] buffer = ArrayPool.Shared.Rent(valueLength); int charsRead = reader.CopyString(buffer); ReadOnlySpan source = buffer.AsSpan(0, charsRead); // Handle the unescaped JSON string. ParseUnescapedString(source); ArrayPool.Shared.Return(buffer, clearArray: true); void ParseUnescapedString(ReadOnlySpan source) { // ... } 另请参阅 如何在 System.Text.Json 中使用 Utf8JsonWriter


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3