Använda Utf8JsonReader i System.Text.Json
Den här artikeln visar hur du kan använda typen Utf8JsonReader för att skapa anpassade parsers och deserializers.
Utf8JsonReader är en högpresterande, låg allokering, framåtriktad läsare för UTF-8-kodad JSON-text. Texten läss från en ReadOnlySpan<byte>
eller ReadOnlySequence<byte>
. Utf8JsonReader
är en lågnivåtyp som kan användas för att skapa anpassade parsers och deserializers. (De JsonSerializer.Deserialize metoder som används Utf8JsonReader
under täcket.)
I följande exempel visas hur du Utf8JsonReader använder klassen. Den här koden förutsätter att variabeln jsonUtf8Bytes
är en bytematris som innehåller giltig JSON, kodad som UTF-8.
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://video2.skills-academy.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Kommentar
Utf8JsonReader
kan inte användas direkt från Visual Basic-kod. Mer information finns i Visual Basic-stöd.
Filtrera data med hjälp av Utf8JsonReader
I följande exempel visas hur du synkront läser en fil och söker efter ett värde.
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<byte> Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF };
public static void Run()
{
// ReadAllBytes if the file encoding is UTF-8:
string fileName = "UniversitiesUtf8.json";
ReadOnlySpan<byte> 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<byte>
//string fileName = "Universities.json";
//string jsonString = File.ReadAllText(fileName);
//ReadOnlySpan<byte> 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://video2.skills-academy.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Koden ovan:
Förutsätter att JSON innehåller en matris med objekt och att varje objekt kan innehålla en namnegenskap av typen sträng.
Räknar objekt och egenskapsvärden för namn som slutar med "University".
Förutsätter att filen är kodad som UTF-16 och omkodar den till UTF-8.
En fil som är kodad som UTF-8 kan läsas direkt i en
ReadOnlySpan<byte>
med hjälp av följande kod:ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
Om filen innehåller en UTF-8 byte order mark (BOM) tar du bort den innan byte skickas till
Utf8JsonReader
, eftersom läsaren förväntar sig text. Annars anses bommen vara ogiltig JSON och läsaren genererar ett undantag.
Här är ett JSON-exempel som föregående kod kan läsa. Det resulterande sammanfattningsmeddelandet är "2 av 4 har namn som slutar med "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"
}
]
Dricks
En asynkron version av det här exemplet finns i JSON-projekt med .NET-exempel.
Läsa från en dataström med hjälp av Utf8JsonReader
När du läser en stor fil (till exempel en gigabyte eller mer i storlek) kanske du vill undvika att läsa in hela filen i minnet på en gång. I det här scenariot kan du använda en FileStream.
När du använder Utf8JsonReader
för att läsa från en ström gäller följande regler:
- Bufferten som innehåller den partiella JSON-nyttolasten måste vara minst lika stor som den största JSON-token i den så att läsaren kan göra framsteg framåt.
- Bufferten måste vara minst lika stor som den största sekvensen av tomt utrymme i JSON.
- Läsaren håller inte reda på de data som den har läst förrän den helt läser nästa TokenType i JSON-nyttolasten. Så när det finns byte kvar i bufferten måste du skicka dem till läsaren igen. Du kan använda BytesConsumed för att avgöra hur många byte som är över.
Följande kod visar hur du läser från en dataström. Exemplet visar en MemoryStream. Liknande kod fungerar med en FileStream, förutom när innehåller FileStream
en UTF-8-struktur i början. I så fall måste du ta bort dessa tre byte från bufferten innan du skickar de återstående byteen Utf8JsonReader
till . Annars skulle läsaren utlösa ett undantag eftersom bommen inte anses vara en giltig del av JSON.
Exempelkoden börjar med en buffert på 4 KB och fördubblar buffertstorleken varje gång den upptäcker att storleken inte är tillräckligt stor för att passa en fullständig JSON-token, vilket krävs för att läsaren ska kunna göra framsteg på JSON-nyttolasten. JSON-exemplet i kodfragmentet utlöser endast en ökning av buffertstorleken om du anger en mycket liten inledande buffertstorlek, till exempel 10 byte. Om du anger den inledande buffertstorleken Console.WriteLine
till 10 illustrerar instruktionerna orsaken till och effekten av att buffertstorleken ökar. Vid den inledande buffertstorleken på 4 KB visas hela JSON-exemplet av varje anrop till Console.WriteLine
, och buffertstorleken behöver aldrig ökas.
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, ref 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, ref buffer, ref reader);
}
// Display value of Summary property, that is, "Hot".
Console.WriteLine($"Got property value: {reader.GetString()}");
}
private static void GetMoreBytesFromStream(
MemoryStream stream, ref byte[] buffer, ref Utf8JsonReader reader)
{
int bytesRead;
if (reader.BytesConsumed < buffer.Length)
{
ReadOnlySpan<byte> 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://video2.skills-academy.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
I föregående exempel anges ingen gräns för hur stor bufferten kan växa. Om tokenstorleken är för stor kan koden misslyckas med ett OutOfMemoryException undantag. Detta kan inträffa om JSON innehåller en token som är cirka 1 GB eller mer i storlek, eftersom en fördubbling av storleken på 1 GB resulterar i en storlek som är för stor för att få plats i en int32
buffert.
ref struct-begränsningar
Eftersom typen är en har den Utf8JsonReader
vissa begränsningar.ref struct
Det kan till exempel inte lagras som ett fält på en annan klass eller struct än en ref struct
.
För att uppnå höga prestanda Utf8JsonReader
måste vara en ref struct
, eftersom den måste cachelagrar indata ReadOnlySpan<byte> (som i sig är en ref struct
). Dessutom är typen Utf8JsonReader
föränderlig eftersom den innehåller tillstånd. Skicka det därför med referens i stället för efter värde. Om värdet skickas Utf8JsonReader
resulterar det i en struct-kopia och tillståndsändringarna visas inte för anroparen.
Mer information om hur du använder referensstrukturer finns i Undvik allokeringar.
Läsa UTF-8-text
För att uppnå bästa möjliga prestanda när du använder läser Utf8JsonReader
du JSON-nyttolaster som redan kodas som UTF-8-text i stället för UTF-16-strängar. Ett kodexempel finns i Filtrera data med Utf8JsonReader.
Läsa med ReadOnlySequence i flera segment
Om JSON-indata är en ReadOnlySpan-byte<> kan varje JSON-element nås från ValueSpan
egenskapen på läsaren när du går igenom läsloopen. Men om dina indata är en ReadOnlySequence-byte<> (vilket är resultatet av läsning från en PipeReader), kan vissa JSON-element korsa flera segment av ReadOnlySequence<byte>
objektet. Dessa element skulle inte vara tillgängliga från ValueSpan i ett sammanhängande minnesblock. När du har ett flera segment ReadOnlySequence<byte>
som indata avsöker HasValueSequence du i stället egenskapen på läsaren för att ta reda på hur du kommer åt det aktuella JSON-elementet. Här är ett rekommenderat mönster:
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
Läsa flera JSON-dokument
I .NET 9 och senare versioner kan du läsa flera, blankstegsavgränsade JSON-dokument från en enda buffert eller dataström. Som standard Utf8JsonReader
utlöser ett undantag om det identifierar tecken som inte är blanksteg som följer det första dokumentet på den översta nivån. Du kan dock konfigurera det beteendet med hjälp av JsonReaderOptions.AllowMultipleValues flaggan.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("null {} 1 \r\n [1,2,3]"u8, options);
reader.Read();
Console.WriteLine(reader.TokenType); // Null
reader.Read();
Console.WriteLine(reader.TokenType); // StartObject
reader.Skip();
reader.Read();
Console.WriteLine(reader.TokenType); // Number
reader.Read();
Console.WriteLine(reader.TokenType); // StartArray
reader.Skip();
Console.WriteLine(reader.Read()); // False
När AllowMultipleValues är inställt på true
kan du också läsa JSON från nyttolaster som innehåller avslutande data som är ogiltiga JSON.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3] <NotJson/>"u8, options);
reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.
Om du vill strömma flera värden på den översta nivån använder du DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken) eller DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken) överlagrar. Som standard DeserializeAsyncEnumerable
försöker strömma element som finns i en enda JSON-matris på den översta nivån. Skicka true
för parametern topLevelValues
för att strömma flera värden på den översta nivån.
ReadOnlySpan<byte> utf8Json = """[0] [0,1] [0,1,1] [0,1,1,2] [0,1,1,2,3]"""u8;
using var stream = new MemoryStream(utf8Json.ToArray());
var items = JsonSerializer.DeserializeAsyncEnumerable<int[]>(stream, topLevelValues: true);
await foreach (int[] item in items)
{
Console.WriteLine(item.Length);
}
/* This snippet produces the following output:
*
* 1
* 2
* 3
* 4
* 5
*/
Uppslag för egenskapsnamn
Om du vill söka efter egenskapsnamn använder ValueSpan du inte för att göra byte-för-byte-jämförelser genom att anropa SequenceEqual. Anropa ValueTextEqualsi stället , eftersom den här metoden tar bort alla tecken som är undantagna i JSON. Här är ett exempel som visar hur du söker efter en egenskap med namnet "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;
}
}
Läsa null-värden i null-värdetyper
De inbyggda System.Text.Json
API:erna returnerar endast icke-nullbara värdetyper. Returnerar Utf8JsonReader.GetBoolean till exempel en bool
. Det genererar ett undantag om det hittar Null
i JSON. I följande exempel visas två sätt att hantera nullvärden, ett genom att returnera en nullbar värdetyp och ett genom att returnera standardvärdet:
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();
}
Hoppa över underordnade token
Använd metoden Utf8JsonReader.Skip() för att hoppa över underordnade till den aktuella JSON-token. Om tokentypen är JsonTokenType.PropertyNameflyttas läsaren till egenskapsvärdet. Följande kodfragment visar ett exempel på hur du använder Utf8JsonReader.Skip() för att flytta läsaren till värdet för en egenskap.
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Summary = "Hot"
};
byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast);
var reader = new Utf8JsonReader(jsonUtf8Bytes);
int temp;
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
{
if (reader.ValueTextEquals("TemperatureCelsius"))
{
reader.Skip();
temp = reader.GetInt32();
Console.WriteLine($"Temperature is {temp} degrees.");
}
continue;
}
default:
continue;
}
}
Använda avkodade JSON-strängar
Från och med .NET 7 kan du använda Utf8JsonReader.CopyString metoden i stället för att använda en avkodad Utf8JsonReader.GetString() JSON-sträng. Till skillnad från GetString(), som alltid allokerar en ny sträng, CopyString kan du kopiera den ej kapslade strängen till en buffert som du äger. Följande kodfragment visar ett exempel på hur du använder en UTF-16-sträng med .CopyString
var reader = new Utf8JsonReader( /* jsonReadOnlySpan */ );
int valueLength = reader.HasValueSequence
? checked((int)reader.ValueSequence.Length)
: reader.ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.AsSpan(0, charsRead);
// Handle the unescaped JSON string.
ParseUnescapedString(source);
ArrayPool<char>.Shared.Return(buffer, clearArray: true);
void ParseUnescapedString(ReadOnlySpan<char> source)
{
// ...
}
Relaterade API:er
Om du vill deserialisera en anpassad typ från en
Utf8JsonReader
instans anropar JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions) du eller JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonTypeInfo<TValue>). Ett exempel finns i Deserialize from UTF-8 (Deserialisera från UTF-8).JsonNode och de klasser som härleds från den ger möjlighet att skapa en föränderlig DOM. Du kan konvertera en
Utf8JsonReader
instans till enJsonNode
genom att anropa JsonNode.Parse(Utf8JsonReader, Nullable<JsonNodeOptions>). Följande kodfragment visar ett exempel.using System.Text.Json; using System.Text.Json.Nodes; namespace Utf8ReaderToJsonNode { public class WeatherForecast { public DateTimeOffset Date { get; set; } public int TemperatureCelsius { get; set; } public string? Summary { get; set; } } public class Program { public static void Main() { var weatherForecast = new WeatherForecast { Date = DateTime.Parse("2019-08-01"), TemperatureCelsius = 25, Summary = "Hot" }; byte[] jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast); var utf8Reader = new Utf8JsonReader(jsonUtf8Bytes); JsonNode? node = JsonNode.Parse(ref utf8Reader); Console.WriteLine(node); } } }
JsonDocument ger möjlighet att skapa en skrivskyddad DOM med hjälp
Utf8JsonReader
av . JsonDocument.ParseValue(Utf8JsonReader) Anropa metoden för att parsa enJsonDocument
från enUtf8JsonReader
instans. Du kan komma åt JSON-elementen som skriver nyttolasten via JsonElement typen . Exempelkod som använder JsonDocument.ParseValue(Utf8JsonReader)finns i RoundtripDataTable.cs och kodfragmentet i Deserialize inferred types to object properties (Deserialize inferred types to object properties).Du kan också parsa en
Utf8JsonReader
instans till ett JsonElement, som representerar ett specifikt JSON-värde genom att anropa JsonElement.ParseValue(Utf8JsonReader).