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, odczytywany z elementu ReadOnlySpan<byte> lub ReadOnlySequence<byte>. Jest Utf8JsonReader to typ niskiego poziomu, który może służyć do tworzenia niestandardowych parserów i deserializatorów. Metody JsonSerializer.Deserialize są używane Utf8JsonReader w ramach okładek.

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.

W poniższym przykładzie pokazano, jak używać Utf8JsonReader klasy:

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

Powyższy kod zakłada, że jsonUtf8 zmienna jest tablicą bajtów zawierającą prawidłowy kod JSON zakodowany jako UTF-8.

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

Aby zapoznać się z asynchroniczną wersją tego przykładu, zobacz projekt JSON przykładów dla platformy .NET.

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 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":

[
  {
    "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"
  }
]

Odczytywanie ze strumienia przy użyciu polecenia Utf8JsonReader

Podczas odczytywania dużego pliku (na przykład rozmiaru gigabajta lub większej ilości) możesz uniknąć konieczności ł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żdy Console.WriteLineelement , 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 jest strukturą ref, ma pewne ograniczenia. Na przykład nie można go przechowywać jako pola w klasie lub strukturę inną niż struktura ref.

Aby osiągnąć wysoką wydajność, ten typ musi być typu ref struct , ponieważ musi buforować wejściowy bajt> ReadOnlySpan<, który sam jest strukturą ref. 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 jej według wartości spowoduje skopiowanie struktury, a zmiany stanu nie będą 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;
        // ...
    }
}

Użyj właściwości ValueTextEquals, aby wyszukać nazwy właściwości

Nie używaj ValueSpan do porównywania bajtów bajtów po bajtach, wywołując SequenceEqual wyszukiwanie nazw właściwości. Wywołaj ValueTextEquals zamiast tego metodę, ponieważ ta metoda usuwa wszystkie znaki, które są ucieczki 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)
{
    // ...
}

Zobacz też