How to use Utf8JsonReader in (Jak używać utf8JsonReader w programie System.Text.Json
W tym artykule pokazano, jak używać Utf8JsonReader typu do tworzenia niestandardowych analizatorów i deserializacji.
Utf8JsonReader to wysokowydajny, niski przydział, czytnik tylko do przodu dla tekstu JSON zakodowanego w formacie UTF-8. Tekst jest odczytywany z elementu ReadOnlySpan<byte>
lub ReadOnlySequence<byte>
. Utf8JsonReader
jest typem niskiego poziomu, który może służyć do tworzenia niestandardowych analizatorów i deserializacji. (Metody JsonSerializer.Deserialize są używane Utf8JsonReader
w ramach okładek).
W poniższym przykładzie pokazano, jak używać Utf8JsonReader klasy . Ten kod zakłada, że zmienna jsonUtf8Bytes
jest tablicą bajtów zawierającą prawidłowy kod JSON zakodowany jako 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://learn.microsoft.com/dotnet/standard/serialization/system-text-json-how-to#visual-basic-support
Uwaga
Utf8JsonReader
Nie można używać bezpośrednio z poziomu programu Visual Basic Code. Aby uzyskać więcej informacji, zobacz Obsługa języka Visual Basic.
Filtrowanie danych przy użyciu Utf8JsonReader
W poniższym przykładzie pokazano, jak synchronicznie odczytać plik i wyszukać wartość.
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
Powyższy kod ma następujące działanie:
Przyjęto założenie, że kod JSON zawiera tablicę obiektów, a każdy obiekt może zawierać właściwość "name" ciągu typu.
Zlicza obiekty i wartości właściwości "name", które kończą się ciągiem "Uniwersytet".
Zakłada, że plik jest zakodowany jako UTF-16 i transkoduje go do formatu UTF-8.
Plik zakodowany jako UTF-8 można odczytać bezpośrednio do obiektu
ReadOnlySpan<byte>
przy użyciu następującego kodu:ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
Jeśli plik zawiera znacznik kolejności bajtów UTF-8 (BOM), usuń go przed przekazaniem bajtów do
Utf8JsonReader
elementu , ponieważ czytelnik oczekuje tekstu. W przeciwnym razie model BOM jest uznawany za nieprawidłowy kod JSON, a czytelnik zgłasza wyjątek.
Oto przykładowy kod JSON, który można odczytać z poprzedniego kodu. Wynikowy komunikat podsumowania to "2 na 4 mają nazwy, które kończą się ciągiem "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"
}
]
Napiwek
Aby zapoznać się z asynchroniczną wersją tego przykładu, zobacz projekt JSON przykładów dla platformy .NET.
Odczytywanie ze strumienia przy użyciu polecenia Utf8JsonReader
Podczas odczytywania dużego pliku (na przykład rozmiaru gigabajta lub większej liczby) możesz uniknąć ładowania całego pliku do pamięci jednocześnie. W tym scenariuszu można użyć elementu FileStream.
W przypadku używania elementu Utf8JsonReader
do odczytu ze strumienia obowiązują następujące reguły:
- Bufor zawierający częściowy ładunek JSON musi być co najmniej tak duży, jak największy w nim token JSON, aby umożliwić czytelnikowi postęp.
- Bufor musi być co najmniej tak duży, jak największa sekwencja białych znaków w formacie JSON.
- Czytelnik nie śledzi danych, które odczytuje, dopóki nie zostanie całkowicie odczytany w ładunku TokenType JSON. Więc gdy w buforze są pozostawione bajty, należy je ponownie przekazać do czytnika. Możesz użyć BytesConsumed polecenia , aby określić, ile bajtów pozostało.
Poniższy kod ilustruje sposób odczytywania ze strumienia. W przykładzie pokazano element MemoryStream. Podobny kod będzie działać z elementem FileStream, z wyjątkiem sytuacji, gdy element FileStream
zawiera kod UTF-8 BOM na początku. W takim przypadku należy usunąć te trzy bajty z buforu przed przekazaniem pozostałych bajtów do .Utf8JsonReader
W przeciwnym razie czytelnik zgłosi wyjątek, ponieważ model BOM nie jest uważany za prawidłową część kodu JSON.
Przykładowy kod rozpoczyna się od buforu o rozmiarze 4 KB i podwaja rozmiar buforu za każdym razem, gdy stwierdza, że rozmiar nie jest wystarczająco duży, aby zmieścić pełny token JSON, który jest wymagany dla czytelnika, aby kontynuować postęp w ładunku JSON. Przykład JSON podany w fragmencie kodu wyzwala wzrost rozmiaru buforu tylko wtedy, gdy ustawisz bardzo mały rozmiar buforu początkowego, na przykład 10 bajtów. W przypadku ustawienia początkowego rozmiaru buforu na 10 Console.WriteLine
instrukcje ilustrują przyczynę i wpływ rozmiaru buforu. Przy początkowym rozmiarze buforu o rozmiarze 4 KB cały przykładowy kod JSON jest wyświetlany przez każde wywołanie metody Console.WriteLine
, a rozmiar buforu nigdy nie musi być zwiększany.
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
W poprzednim przykładzie nie określono limitu wielkości buforu. Jeśli rozmiar tokenu jest zbyt duży, kod może zakończyć się niepowodzeniem OutOfMemoryException z wyjątkiem. Może się tak zdarzyć, jeśli plik JSON zawiera token o rozmiarze około 1 GB lub większym, ponieważ podwojenie rozmiaru 1 GB powoduje, że rozmiar jest zbyt duży, aby zmieścić się w buforze int32
.
ograniczenia struktury ref
Utf8JsonReader
Ponieważ typ to ref struct
, ma pewne ograniczenia. Na przykład nie można go przechowywać jako pola w klasie lub strukturę inną ref struct
niż .
Aby osiągnąć wysoką wydajność, Utf8JsonReader
musi być elementem ref struct
, ponieważ musi buforować wejściowy bajt> ReadOnlySpan<(który sam jest wartością ref struct
). Ponadto typ jest modyfikowalny, Utf8JsonReader
ponieważ przechowuje stan. W związku z tym należy przekazać go przez odwołanie, a nie przez wartość. Przekazanie Utf8JsonReader
wartości by spowodowałoby skopiowanie struktury, a zmiany stanu nie byłyby widoczne dla elementu wywołującego.
Aby uzyskać więcej informacji na temat używania struktur ref, zobacz Unikanie alokacji.
Odczytywanie tekstu UTF-8
Aby osiągnąć najlepszą możliwą wydajność podczas korzystania z polecenia Utf8JsonReader
, odczyt ładunków JSON już zakodowanych jako tekst UTF-8, a nie jako ciągi UTF-16. Aby zapoznać się z przykładem kodu, zobacz Filter data using Utf8JsonReader (Filtrowanie danych przy użyciu elementu Utf8JsonReader).
Odczyt za pomocą funkcji ReadOnlySequence z wieloma segmentami
Jeśli dane wejściowe JSON są bajtem >ReadOnlySpan<, dostęp do każdego elementu JSON można uzyskać z ValueSpan
właściwości czytnika podczas przechodzenia przez pętlę odczytu. Jeśli jednak dane wejściowe są bajtem > ReadOnlySequence<(co jest wynikiem odczytu z PipeReaderelementu ), niektóre elementy JSON mogą zawierać wiele segmentów ReadOnlySequence<byte>
obiektu. Te elementy nie będą dostępne w ValueSpan ciągłym bloku pamięci. Zamiast tego za każdym razem, gdy masz wiele segmentów ReadOnlySequence<byte>
jako dane wejściowe, sonduj HasValueSequence właściwość na czytniku, aby dowiedzieć się, jak uzyskać dostęp do bieżącego elementu JSON. Oto zalecany wzorzec:
while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}
Odczytywanie wielu dokumentów JSON
Na platformie .NET 9 i nowszych wersjach można odczytać wiele dokumentów JSON rozdzielonych odstępami od jednego buforu lub strumienia. Domyślnie zgłasza wyjątek, Utf8JsonReader
jeśli wykryje znaki inne niż białe znaki, które są na wierzchołka pierwszego poziomu. Można jednak skonfigurować to zachowanie przy użyciu flagi JsonReaderOptions.AllowMultipleValues .
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
Gdy AllowMultipleValues jest ustawiona wartość true
, możesz również odczytywać dane JSON z ładunków zawierających końcowe dane, które są nieprawidłowe w formacie JSON.
JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3] <NotJson/>"u8, options);
reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.
Aby przesłać strumieniowo wiele wartości najwyższego poziomu, użyj DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken) przeciążenia lub DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken) . Domyślnie DeserializeAsyncEnumerable
próbuje przesłać strumieniowo elementy, które znajdują się w pojedynczej tablicy JSON najwyższego poziomu. Przekaż true
parametr, aby przesłać strumieniowo topLevelValues
wiele wartości najwyższego poziomu.
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
*/
Wyszukiwania nazw właściwości
Aby wyszukać nazwy właściwości, nie należy używać ValueSpan do wykonywania porównań bajtów bajtów po bajtach przez wywołanie metody SequenceEqual. Zamiast tego wywołaj metodę ValueTextEquals, ponieważ ta metoda nie zawiera żadnych znaków, które zostały uniknięci w formacie JSON. Oto przykład pokazujący, jak wyszukać właściwość o nazwie "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;
}
}
Odczytywanie wartości null do typów wartości dopuszczanych do wartości null
Wbudowane System.Text.Json
interfejsy API zwracają tylko typy wartości innych niż null. Na przykład Utf8JsonReader.GetBoolean zwraca wartość bool
. Zgłasza wyjątek w przypadku znalezienia Null
go w formacie JSON. W poniższych przykładach pokazano dwa sposoby obsługi wartości null: jeden, zwracając typ wartości dopuszczającej wartość null i jeden, zwracając wartość domyślną:
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();
}
Pomiń elementy podrzędne tokenu
Utf8JsonReader.Skip() Użyj metody , aby pominąć elementy podrzędne bieżącego tokenu JSON. Jeśli typ tokenu to JsonTokenType.PropertyName, czytelnik przechodzi do wartości właściwości. Poniższy fragment kodu przedstawia przykład użycia w Utf8JsonReader.Skip() celu przeniesienia czytnika do wartości właściwości.
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;
}
}
Używanie zdekodowanych ciągów JSON
Począwszy od platformy .NET 7, można użyć Utf8JsonReader.CopyString metody zamiast Utf8JsonReader.GetString() używać zdekodowanego ciągu JSON. W przeciwieństwie do GetString()elementu , który zawsze przydziela nowy ciąg, CopyString umożliwia skopiowanie niezasłanianego ciągu do buforu, którego jesteś właścicielem. Poniższy fragment kodu przedstawia przykład korzystania z ciągu UTF-16 przy użyciu polecenia 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)
{
// ...
}
Powiązane interfejsy API
Aby wykonać deserializowanie typu niestandardowego z wystąpienia, wywołaj metodę
Utf8JsonReader
JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonSerializerOptions) lub JsonSerializer.Deserialize<TValue>(Utf8JsonReader, JsonTypeInfo<TValue>). Aby zapoznać się z przykładem, zobacz Deserialize z formatu UTF-8.JsonNode oraz klasy, które pochodzą z niego, zapewniają możliwość tworzenia modyfikowalnego modelu DOM. Wystąpienie można przekonwertować
Utf8JsonReader
naJsonNode
wystąpienie, wywołując polecenie JsonNode.Parse(Utf8JsonReader, Nullable<JsonNodeOptions>). Poniższy fragment kodu przedstawia przykład.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 zapewnia możliwość tworzenia modelu DOM tylko do odczytu przy użyciu polecenia
Utf8JsonReader
. Wywołaj metodę JsonDocument.ParseValue(Utf8JsonReader) , aby przeanalizować elementJsonDocument
zUtf8JsonReader
wystąpienia. Dostęp do elementów JSON tworzących ładunek można uzyskać za pomocą JsonElement typu . Na przykład kod, który używa JsonDocument.ParseValue(Utf8JsonReader)metody , zobacz RoundtripDataTable.cs i fragment kodu w deserialize wnioskowania typów do właściwości obiektów.Możesz również przeanalizować
Utf8JsonReader
wystąpienie do JsonElementklasy , która reprezentuje określoną wartość JSON, wywołując metodę JsonElement.ParseValue(Utf8JsonReader).