How to use Utf8JsonReader in (Jak używać utf8JsonReader w programie System.Text.Json
Artykuł
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:
Jeśli plik zawiera znacznik kolejności bajtów UTF-8 (BOM), usuń go przed przekazaniem bajtów do Utf8JsonReaderelementu , 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":
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 structniż .
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.
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:
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 .
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.
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":
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)
{
// ...
}
JsonNode oraz klasy, które pochodzą z niego, zapewniają możliwość tworzenia modyfikowalnego modelu DOM. Wystąpienie można przekonwertować Utf8JsonReader na JsonNode 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);
}
}
}
Źródło tej zawartości można znaleźć w witrynie GitHub, gdzie można również tworzyć i przeglądać problemy i żądania ściągnięcia. Więcej informacji znajdziesz w naszym przewodniku dla współtwórców.
Opinia o produkcie .NET
.NET to projekt typu open source. Wybierz link, aby przekazać opinię:
Dołącz do serii meetup, aby tworzyć skalowalne rozwiązania sztucznej inteligencji oparte na rzeczywistych przypadkach użycia z innymi deweloperami i ekspertami.