Teilen über


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)
{
    // ...
}

Siehe auch