Share via


Utf8JsonReader gebruiken in System.Text.Json

In dit artikel wordt beschreven hoe u het type kunt gebruiken voor het Utf8JsonReader bouwen van aangepaste parsers en ontserialisaties.

Utf8JsonReader is een krachtige, lage toewijzing, alleen-doorstuurlezer voor UTF-8 gecodeerde JSON-tekst. De tekst wordt gelezen uit een ReadOnlySpan<byte> of ReadOnlySequence<byte>. Utf8JsonReader is een type op laag niveau dat kan worden gebruikt om aangepaste parsers en ontserialisaties te bouwen. (De JsonSerializer.Deserialize methoden die onder de dekking worden gebruikt Utf8JsonReader .)

In het volgende voorbeeld ziet u hoe u de Utf8JsonReader klasse gebruikt. Deze code gaat ervan uit dat de jsonUtf8Bytes variabele een bytematrix is die geldige JSON bevat, gecodeerd als 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

Notitie

Utf8JsonReader kan niet rechtstreeks vanuit Visual Basic-code worden gebruikt. Zie Visual Basic-ondersteuning voor meer informatie.

Gegevens filteren met Utf8JsonReader

In het volgende voorbeeld ziet u hoe u een bestand synchroon kunt lezen en een waarde kunt zoeken.

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

Met de voorgaande code wordt:

  • Stel dat de JSON een matrix met objecten bevat en dat elk object een eigenschap 'name' van het type tekenreeks kan bevatten.

  • Hiermee worden objecten en 'naam'-eigenschapswaarden geteld die eindigen op 'Universiteit'.

  • Wordt ervan uitgegaan dat het bestand is gecodeerd als UTF-16 en transcodeert in UTF-8.

    Een bestand dat als UTF-8 is gecodeerd, kan rechtstreeks in een ReadOnlySpan<byte> bestand worden gelezen met behulp van de volgende code:

    ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);
    

    Als het bestand een UTF-8 byte ordermarkering (BOM) bevat, verwijdert u het voordat u de bytes doorgeeft aan de Utf8JsonReader, omdat de lezer tekst verwacht. Anders wordt de BOM beschouwd als ongeldige JSON en genereert de lezer een uitzondering.

Hier volgt een JSON-voorbeeld dat de voorgaande code kan lezen. Het resulterende samenvattingsbericht is '2 van de 4 hebben namen die eindigen op 'Universiteit':

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

Tip

Zie het JSON-project met .NET-voorbeelden voor een asynchrone versie van dit voorbeeld.

Lezen uit een stream met behulp van Utf8JsonReader

Bij het lezen van een groot bestand (bijvoorbeeld een gigabyte of meer in grootte), wilt u het hele bestand mogelijk in één keer in het geheugen laden. Voor dit scenario kunt u een FileStream.

Wanneer u de Utf8JsonReader stroom gebruikt om te lezen vanuit een stream, zijn de volgende regels van toepassing:

  • De buffer met de gedeeltelijke JSON-nettolading moet ten minste zo groot zijn als het grootste JSON-token erin, zodat de lezer vooruit kan gaan.
  • De buffer moet ten minste zo groot zijn als de grootste reeks witruimte binnen de JSON.
  • De lezer houdt de gegevens die worden gelezen pas bij als de volgende TokenType in de JSON-nettolading volledig wordt gelezen. Dus wanneer er bytes overblijven in de buffer, moet u ze opnieuw doorgeven aan de lezer. U kunt gebruiken BytesConsumed om te bepalen hoeveel bytes er nog over zijn.

De volgende code illustreert hoe u vanuit een stream kunt lezen. In het voorbeeld ziet u een MemoryStream. Vergelijkbare code werkt met een FileStream, behalve wanneer de FileStream bom UTF-8 aan het begin bevat. In dat geval moet u deze drie bytes uit de buffer verwijderen voordat u de resterende bytes doorgeeft aan de Utf8JsonReader. Anders zou de lezer een uitzondering genereren, omdat de BOM niet als een geldig deel van de JSON wordt beschouwd.

De voorbeeldcode begint met een buffer van 4 kB en verdubbelt de buffergrootte telkens wanneer de grootte niet groot genoeg is om een volledig JSON-token aan te passen. Dit is vereist voor de lezer om de voortgang van de JSON-nettolading door te voeren. Het JSON-voorbeeld in het fragment activeert alleen een buffergrootte als u een zeer kleine initiële buffergrootte instelt, bijvoorbeeld 10 bytes. Als u de initiële buffergrootte instelt op 10, illustreren de Console.WriteLine instructies de oorzaak en het effect van de buffergrootte. Bij de initiële buffergrootte van 4 kB wordt de volledige JSON-voorbeeld weergegeven door elke aanroep naar Console.WriteLineen hoeft de buffergrootte nooit te worden verhoogd.

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

In het voorgaande voorbeeld wordt geen limiet ingesteld voor hoe groot de buffer kan worden. Als de tokengrootte te groot is, kan de code mislukken met een OutOfMemoryException uitzondering. Dit kan gebeuren als de JSON een token bevat dat ongeveer 1 GB of groter is, omdat het verdubbelen van de grootte van 1 GB resulteert in een grootte die te groot is om in een int32 buffer te passen.

beperkingen voor verw

Omdat het Utf8JsonReader type een ref structis, heeft het bepaalde beperkingen. Het kan bijvoorbeeld niet worden opgeslagen als een veld in een klasse of een andere struct dan een ref struct.

Om hoge prestaties te bereiken, Utf8JsonReader moet een ref struct, omdat het de invoer ReadOnlySpan<byte> moet cachen (wat zelf een ref structis ). Bovendien is het Utf8JsonReader type veranderlijk omdat het de status bevat. Geef deze daarom door aan een verwijzing in plaats van op waarde. Het doorgeven van de Utf8JsonReader waarde zou resulteren in een struct-kopie en de statuswijzigingen zouden niet zichtbaar zijn voor de aanroeper.

Zie Toewijzingen voorkomen voor meer informatie over het gebruik van verw-structs.

UTF-8-tekst lezen

Lees JSON-nettoladingen die al zijn gecodeerd als UTF-8-tekst in plaats van als UTF-16-tekenreeksen om de best mogelijke prestaties te bereiken tijdens het gebruik Utf8JsonReader. Zie Gegevens filteren met Utf8JsonReader voor een codevoorbeeld.

Lezen met ReadOnlySequence met meerdere segmenten

Als uw JSON-invoer een ReadOnlySpan-byte<> is, kan elk JSON-element worden geopend vanuit de ValueSpan eigenschap op de lezer terwijl u de leeslus doorloopt. Als uw invoer echter een ReadOnlySequence-byte<> is (wat het resultaat is van het lezen van eenPipeReader), kunnen sommige JSON-elementen meerdere segmenten van het ReadOnlySequence<byte> object verdwaald raken. Deze elementen zijn niet toegankelijk vanuit ValueSpan een aaneengesloten geheugenblok. Wanneer u een multisegment als invoer hebt, moet u de HasValueSequence eigenschap van de lezer peilen om erachter te komen hoe u toegang krijgt tot het huidige JSON-elementReadOnlySequence<byte>. Hier volgt een aanbevolen patroon:

while (reader.Read())
{
    switch (reader.TokenType)
    {
        // ...
        ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
            reader.ValueSequence.ToArray() :
            reader.ValueSpan;
        // ...
    }
}

Meerdere JSON-documenten lezen

In .NET 9 en latere versies kunt u meerdere, witruimte gescheiden JSON-documenten uit één buffer of stream lezen. Genereert standaard Utf8JsonReader een uitzondering als er niet-witruimtetekens worden gedetecteerd die het eerste document op het hoogste niveau volgen. U kunt dit gedrag echter configureren met behulp van de JsonReaderOptions.AllowMultipleValues vlag.

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

Wanneer AllowMultipleValues dit is ingesteld true, kunt u ook JSON lezen uit nettoladingen die volggegevens bevatten die ongeldige JSON bevatten.

JsonReaderOptions options = new() { AllowMultipleValues = true };
Utf8JsonReader reader = new("[1,2,3]    <NotJson/>"u8, options);

reader.Read();
reader.Skip(); // Succeeds.
reader.Read(); // Throws JsonReaderException.

Als u meerdere waarden op het hoogste niveau wilt streamen, gebruikt u de DeserializeAsyncEnumerable<TValue>(Stream, Boolean, JsonSerializerOptions, CancellationToken) of DeserializeAsyncEnumerable<TValue>(Stream, JsonTypeInfo<TValue>, Boolean, CancellationToken) overbelasting. DeserializeAsyncEnumerable Standaard wordt geprobeerd elementen te streamen die zijn opgenomen in één JSON-matrix op het hoogste niveau. Geef true de topLevelValues parameter door om meerdere waarden op het hoogste niveau te 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
 */

Zoekacties voor eigenschapsnamen

Als u eigenschapsnamen wilt opzoeken, hoeft ValueSpan u geen byte-bytevergelijkingen uit te voeren door aan te roepen SequenceEqual. Roep in plaats ValueTextEqualsdaarvan aan, omdat met deze methode geen tekens worden weergegeven die in de JSON zijn ontsnapt. Hier volgt een voorbeeld waarin wordt getoond hoe u kunt zoeken naar een eigenschap met de naam '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;
    }
}

Null-waarden lezen in typen null-waarden

De ingebouwde System.Text.Json API's retourneren alleen niet-null-waardetypen. Retourneert bijvoorbeeld Utf8JsonReader.GetBoolean een bool. Er wordt een uitzondering gegenereerd als deze in de JSON wordt gevonden Null . In de volgende voorbeelden ziet u twee manieren om null-waarden te verwerken, één door een type null-waarde te retourneren en één door de standaardwaarde te retourneren:

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();
}

Onderliggende elementen van token overslaan

Gebruik de Utf8JsonReader.Skip() methode om de onderliggende items van het huidige JSON-token over te slaan. Als het tokentype is JsonTokenType.PropertyName, wordt de lezer naar de eigenschapswaarde verplaatst. In het volgende codefragment ziet u een voorbeeld van het gebruik Utf8JsonReader.Skip() van de lezer naar de waarde van een eigenschap.

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;
    }
}

Gedecodeerde JSON-tekenreeksen gebruiken

Vanaf .NET 7 kunt u de Utf8JsonReader.CopyString methode gebruiken in plaats van Utf8JsonReader.GetString() een gedecodeerde JSON-tekenreeks te gebruiken. In tegenstelling tot GetString(), waarmee altijd een nieuwe tekenreeks wordt toegewezen, CopyString kunt u de niet-gescapede tekenreeks kopiëren naar een buffer waarvan u de eigenaar bent. In het volgende codefragment ziet u een voorbeeld van het gebruik van een UTF-16-tekenreeks met behulp van 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)
{
    // ...
}

Zie ook