Verwenden von Utf8JsonReader in System.Text.Json
In diesem Artikel erfahren Sie, wie Sie den Utf8JsonReader-Typ zum Erstellen benutzerdefinierter Parser und Deserialisierer verwenden.
Utf8JsonReader ist ein hochleistungsfähiger, sparsamer, nur für von links nach rechts zu lesenden Text geeigneter UTF-8-JSON-Text-Reader. Der Text wird aus einem ReadOnlySpan<byte>
oder ReadOnlySequence<byte>
gelesen. Utf8JsonReader
ist ein Low-Level-Typ, der zum Erstellen angepasster Parser und Deserialisierer verwendet werden kann. (Die JsonSerializer.Deserialize-Methoden verwenden Utf8JsonReader
unter der Haube.)
Im folgenden Beispiel wird die Verwendung der Utf8JsonReader-Klasse veranschaulicht. Dieser Code geht davon aus, dass die Variable jsonUtf8Bytes
ein Byte-Array ist, das gültiges JSON enthält, das als UTF-8 kodiert ist.
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
Hinweis
Utf8JsonReader
kann nicht direkt aus Visual Basic-Code verwendet werden. Weitere Informationen finden Sie unter Visual Basic-Support.
Filtern von Daten mithilfe von Utf8JsonReader
Im folgenden Beispiel wird gezeigt, wie eine Datei synchron gelesen und nach einem Wert gesucht wird.
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://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Der vorangehende Code:
Er geht davon aus, dass das JSON ein Array von Objekten enthält und dass jedes Objekt eine Eigenschaft „name“ vom Typ String enthält.
Zählt Objekte und „name“-Eigenschaftswerte, die auf „University“ enden.
Setzt voraus, dass die Datei UTF-16-codiert ist und transcodiert sie in UTF-8.
Eine UTF-8-codierte Datei kann mithilfe des folgenden Codes direkt in ein
ReadOnlySpan<byte>
gelesen werden:ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
Wenn die Datei eine UTF-8-Bytereihenfolge-Marke (BOM) enthält, entfernen Sie diese, bevor Sie die Bytes an den
Utf8JsonReader
übergeben, da der Reader Text erwartet. Andernfalls wird die BOM als ungültiges JSON betrachtet, und der Reader löst eine Ausnahme aus.
Im Folgenden finden Sie ein JSON-Beispiel, das der voranstehende Code lesen kann. Die resultierende Zusammenfassungsmeldung lautet „2 out of 2 have names that end with ‚University‘“ (2 von 4 besitzen Namen, die auf „University“ enden):
[
{
"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"
}
]
Tipp
Eine asynchrone Version dieses Beispiels finden Sie im .NET-Beispiel-JSON-Projekt.
Lesen aus einem Datenstrom mithilfe von Utf8JsonReader
Wenn Sie eine große Datei lesen (z. B. ein Gigabyte oder mehr), möchten Sie vielleicht vermeiden, die gesamte Datei auf einmal in den Speicher zu laden. In diesem Szenario können Sie einen FileStream verwenden.
Wenn Sie einen Utf8JsonReader
zum Lesen aus einem Stream verwenden, gelten die folgenden Regeln:
- Der Puffer, der die partielle JSON-Nutzlast enthält, muss mindestens so groß wie das größte JSON-Token darin sein, damit der Reader vorankommen kann.
- Der Puffer muss mindestens so groß wie die größte Sequenz von Leerraum innerhalb der JSON-Nutzdaten sein.
- Der Reader verfolgt die gelesenen Daten nicht nach, bis er den nächsten TokenType in der JSON-Nutzlast vollständig gelesen hat. Wenn im Puffer noch Bytes vorhanden sind, müssen Sie sie wieder dem Reader übergeben. Sie können BytesConsumed verwenden, um zu bestimmen, wie viele Bytes übrig bleiben.
Im folgenden Code wird veranschaulicht, wie aus einem Stream gelesen wird. Das Beispiel zeigt einen MemoryStream. Ähnlicher Code funktioniert mit einem FileStream, es sei denn, der FileStream
enthält am Anfang eine UTF-8-BOM. In diesem Fall müssen Sie diese drei Bytes aus dem Puffer entfernen, bevor Sie die verbleibenden Bytes an den Utf8JsonReader
übergeben. Andernfalls würde der Reader eine Ausnahme auslösen, da die BOM nicht als gültiger Teil des JSON-Codes angesehen wird.
Der Beispielcode beginnt mit einem 4 KB großen Puffer und verdoppelt die Puffergröße jedes Mal, wenn festgestellt wird, dass die Größe nicht ausreicht, um ein komplettes JSON-Token aufzunehmen. Dies ist erforderlich, damit der Reader mit den JSON-Nutzdaten vorankommt. Das JSON-Beispiel im Codeausschnitt löst nur dann eine Erhöhung der Puffergröße aus, wenn Sie eine sehr geringe Anfangspuffergröße festlegen, z. B. 10 Bytes. Die Console.WriteLine
-Anweisungen zeigen Ursache und Auswirkung der Puffergrößenerhöhungen, wenn Sie die Anfangspuffergröße auf 10 festlegen. Bei der anfänglichen Puffergröße von 4 KB wird das gesamte Beispiel-JSON bei jedem Aufruf von Console.WriteLine
angezeigt, und die Puffergröße muss nie erhöht werden.
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://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Im vorherigen Beispiel wurde keine Pufferobergrenze festgelegt. Bei übermäßiger Tokengröße kann bei dem Code ein OutOfMemoryException-Ausnahmefehler auftreten. Dies kann vorkommen, wenn der JSON-Code ein ungefähr 1 GB großes oder größeres Token enthält, da das Token bei Verdoppelung von 1 GB nicht mehr in einen int32
-Puffer passt.
Einschränkungen bei „ref struct“
Da der Typ Utf8JsonReader
ein ref struct
ist, hat er bestimmte Einschränkungen. Er kann beispielsweise nicht als Feld einer Klasse oder Struktur gespeichert werden, die nicht ref struct
ist.
Um eine hohe Leistung zu erreichen, muss Utf8JsonReader
ein ref struct
sein, da es die Anforderung hat, die Eingabe ReadOnlySpan<byte> (die selbst ein ref struct
ist) zwischenzuspeichern. Darüber hinaus ist der Typ Utf8JsonReader
änderbar, da er den Zustand enthält. Übergeben Sie ihn daher als Verweis anstatt als Wert. Die Übergabe von Utf8JsonReader
als Wert würde zu einer struct-Kopie führen, und die Statusänderungen wären für den Aufrufer nicht sichtbar.
Weitere Informationen zum Verwenden von ref-Strukturen finden Sie unter Vermeiden von Zuordnungen.
Lesen von UTF-8-Text
Um die bestmögliche Leistung bei Verwendung des Utf8JsonReader
zu erzielen, lesen Sie JSON-Nutzdaten bereits als UTF-8-codierten Text und nicht als UTF-16-Zeichenfolgen. Ein Codebeispiel finden Sie unter Filtern von Daten mithilfe von Utf8JsonReader.
Lesen mit „ReadOnlySequence“ mit mehreren Segmenten
Wenn es sich bei Ihrer JSON-Eingabe um eine ReadOnlySpan<byte>-Struktur handelt, kann auf jedes JSON-Element über die ValueSpan
-Eigenschaft des Readers zugegriffen werden, während Sie die Leseschleife durchlaufen. Wenn Ihre Eingabe jedoch eine ReadOnlySequence<byte>-Struktur ist (was das Ergebnis des Lesens aus einem PipeReader ist), können einige JSON-Elemente mehrere Segmente des ReadOnlySequence<byte>
-Objekts umspannen. Auf diese Elemente könnte nicht über ValueSpan in einem zusammenhängenden Speicherblock zugegriffen werden. Rufen Sie stattdessen, immer wenn Sie ein aus mehreren Segmenten bestehendes ReadOnlySequence<byte>
als Eingabe haben, die HasValueSequence-Eigenschaft des Readers ab, um zu ermitteln, wie der Zugriff auf das aktuelle JSON-Element erfolgen kann. Ein empfohlenes Muster finden Sie hier:
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
Mehrere JSON-Dokumente lesen
In .NET 9 und späteren Versionen können Sie mehrere, durch Leerzeichen getrennte JSON-Dokumente aus einem einzigen Puffer oder Stream lesen. Standardmäßig löst Utf8JsonReader
eine Ausnahme aus, wenn es Non-White-Space-Zeichen entdeckt, die sich hinter dem ersten Top-Level-Dokument befinden. Sie können dieses Verhalten jedoch mit dem Flag JsonReaderOptions.AllowMultipleValues konfigurieren.
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
Wenn AllowMultipleValues auf true
festgelegt ist, können Sie auch JSON aus Payloads lesen, die nachgestellte Daten enthalten, die ungültiges JSON sind.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3] <NotJson/>"u8, options);
reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.
Um mehrere Top-Level-Werte zu streamen, verwenden Sie die DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken)-oder DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken)-Überladung. Standardmäßig versucht DeserializeAsyncEnumerable
, Elemente zu streamen, die in einem einzigen Top-Level-JSON-Array enthalten sind. Übergeben Sie true
für den Parameter topLevelValues
, um mehrere Top-Level-Werte zu streamen.
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
*/
Nachschlagen von Eigenschaftsnamen
Um Eigenschaftsnamen nachzuschlagen, verwenden Sie nicht ValueSpan, um Byte-für-Byte-Vergleiche durch den Aufruf von SequenceEqual durchzuführen. Rufen Sie stattdessen ValueTextEquals auf, denn diese Methode entfernt sämtliche Maskierungszeichen, die im JSON maskiert sind. Hier finden Sie ein Beispiel, das zeigt, wie Sie nach einer Eigenschaft suchen, die „name“ heißt:
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;
}
}
Lesen von Nullwerten in Nullable-Werttypen
Die integrierten System.Text.Json
-APIs geben nur Non-Nullable-Werttypen zurück. Utf8JsonReader.GetBoolean gibt beispielsweise einen bool
zurück. Wenn es Null
in JSON findet, löst es eine Ausnahme aus. In den folgenden Beispielen werden zwei Möglichkeiten zum Behandeln von Nullwerten veranschaulicht, eine, bei der ein Nullable-Werttyp zurückgegeben wird, und eine andere, bei der der Standardwert zurückgegeben wird:
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();
}
Überspringen von untergeordneten Elementen des Tokens
Verwenden Sie die Utf8JsonReader.Skip()-Methode, um die untergeordneten Elemente des aktuellen JSON-Tokens zu überspringen. Wenn der Tokentyp JsonTokenType.PropertyName lautet, wechselt der Reader zum Eigenschaftswert. Der folgende Codeschnipsel zeigt ein Beispiel für die Verwendung des Utf8JsonReader.Skip() zum Verlagern des Readers auf den Wert einer Eigenschaft.
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;
}
}
Verwenden decodierter JSON-Zeichenfolgen
Ab .NET 7 können Sie die Utf8JsonReader.CopyString-Methode anstelle von Utf8JsonReader.GetString() nutzen, um eine decodierte JSON-Zeichenfolge zu verwenden. Im Gegensatz zur GetString()-Methode, bei der immer eine neue Zeichenfolge zugeordnet wird, können Sie bei CopyString die Zeichenfolge ohne Escapezeichen in einen Puffer kopieren, den Sie besitzen. Der folgende Codeausschnitt zeigt ein Beispiel für die Verwendung einer UTF-16-Zeichenfolge mit 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)
{
// ...
}
Zugehörige APIs
Zum Deserialisieren eines benutzerdefinierten Typs aus einer
Utf8JsonReader
-Instanz, rufen Sie JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions) oder JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonTypeInfo<TValue>) auf. Ein Beispiel finden Sie unter Deserialisieren von UTF-8.JsonNode und die davon abgeleiteten Klassen bieten die Möglichkeit, ein änderbares DOM zu erstellen. Sie können eine
Utf8JsonReader
-Instanz in einenJsonNode
konvertieren, indem Sie JsonNode.Parse(Utf8JsonReader, Nullable<JsonNodeOptions>) aufrufen. Der folgende Codeausschnitt zeigt ein Beispiel.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 bietet die Möglichkeit, ein schreibgeschütztes DOM mithilfe von
Utf8JsonReader
zu erstellen. Rufen Sie die JsonDocument.ParseValue(Utf8JsonReader)-Methode auf, um einJsonDocument
aus einerUtf8JsonReader
-Instanz zu parsen. Auf die JSON-Elemente, aus denen sich die Nutzdaten zusammensetzen, können Sie über den Typ JsonElement zugreifen. Beispielcode, der JsonDocument.ParseValue(Utf8JsonReader) verwendet, finden Sie unter RoundtripDataTable.cs und im Codeschnipsel in Deserialisieren abgeleiteter Typen in Objekteigenschaften.Sie können eine
Utf8JsonReader
-Instanz auch in ein JsonElement parsen, das einen bestimmten JSON-Wert darstellt, indem Sie JsonElement.ParseValue(Utf8JsonReader) aufrufen.