Share via


Använda Utf8JsonReader i System.Text.Json

Den här artikeln visar hur du kan använda typen Utf8JsonReader för att skapa anpassade parsers och deserializers.

Utf8JsonReader är en högpresterande, låg allokering, framåtriktad läsare för UTF-8-kodad JSON-text, läs från en ReadOnlySpan<byte> eller ReadOnlySequence<byte>. Utf8JsonReader är en lågnivåtyp som kan användas för att skapa anpassade parsers och deserializers. De JsonSerializer.Deserialize metoder som används Utf8JsonReader under täcket.

Utf8JsonReader kan inte användas direkt från Visual Basic-kod. Mer information finns i Visual Basic-stöd.

I följande exempel visas hur du Utf8JsonReader använder klassen:

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

Föregående kod förutsätter att variabeln jsonUtf8 är en bytematris som innehåller giltig JSON, kodad som UTF-8.

Filtrera data med hjälp av Utf8JsonReader

I följande exempel visas hur du synkront läser en fil och söker efter ett värde.

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

En asynkron version av det här exemplet finns i JSON-projekt med .NET-exempel.

Koden ovan:

  • Förutsätter att JSON innehåller en matris med objekt och att varje objekt kan innehålla en namnegenskap av typen sträng.

  • Räknar objekt och egenskapsvärden för namn som slutar med "University".

  • Förutsätter att filen är kodad som UTF-16 och omkodar den till UTF-8. En fil som är kodad som UTF-8 kan läsas direkt i en ReadOnlySpan<byte> med hjälp av följande kod:

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

    Om filen innehåller en UTF-8 byte order mark (BOM) tar du bort den innan byte skickas till Utf8JsonReader, eftersom läsaren förväntar sig text. Annars anses bommen vara ogiltig JSON och läsaren genererar ett undantag.

Här är ett JSON-exempel som föregående kod kan läsa. Det resulterande sammanfattningsmeddelandet är "2 av 4 har namn som slutar med "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"
  }
]

Läsa från en dataström med hjälp av Utf8JsonReader

När du läser en stor fil (till exempel en gigabyte eller mer i storlek) kanske du vill undvika att behöva läsa in hela filen i minnet på en gång. I det här scenariot kan du använda en FileStream.

När du använder Utf8JsonReader för att läsa från en ström gäller följande regler:

  • Bufferten som innehåller den partiella JSON-nyttolasten måste vara minst lika stor som den största JSON-token i den så att läsaren kan göra framsteg framåt.
  • Bufferten måste vara minst lika stor som den största sekvensen av tomt utrymme i JSON.
  • Läsaren håller inte reda på de data som den har läst förrän den helt läser nästa TokenType i JSON-nyttolasten. Så när det finns byte kvar i bufferten måste du skicka dem till läsaren igen. Du kan använda BytesConsumed för att avgöra hur många byte som är över.

Följande kod visar hur du läser från en dataström. Exemplet visar en MemoryStream. Liknande kod fungerar med en FileStream, förutom när innehåller FileStream en UTF-8-struktur i början. I så fall måste du ta bort dessa tre byte från bufferten innan du skickar de återstående byteen Utf8JsonReadertill . Annars skulle läsaren utlösa ett undantag eftersom bommen inte anses vara en giltig del av JSON.

Exempelkoden börjar med en buffert på 4 KB och fördubblar buffertstorleken varje gång den upptäcker att storleken inte är tillräckligt stor för att passa en fullständig JSON-token, vilket krävs för att läsaren ska kunna göra framsteg på JSON-nyttolasten. JSON-exemplet i kodfragmentet utlöser endast en ökning av buffertstorleken om du anger en mycket liten inledande buffertstorlek, till exempel 10 byte. Om du anger den inledande buffertstorleken Console.WriteLine till 10 illustrerar instruktionerna orsaken till och effekten av att buffertstorleken ökar. Vid den inledande buffertstorleken på 4 KB visas hela JSON-exemplet av varje Console.WriteLine, och buffertstorleken behöver aldrig ökas.

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

I föregående exempel anges ingen gräns för hur stor bufferten kan växa. Om tokenstorleken är för stor kan koden misslyckas med ett OutOfMemoryException undantag. Detta kan inträffa om JSON innehåller en token som är cirka 1 GB eller mer i storlek, eftersom en fördubbling av storleken på 1 GB resulterar i en storlek som är för stor för att få plats i en int32 buffert.

ref struct-begränsningar

Eftersom typen Utf8JsonReader är en referens-struct har den vissa begränsningar. Det kan till exempel inte lagras som ett fält på en annan klass eller struct än en referens-struct.

För att uppnå höga prestanda måste den här typen vara en ref struct eftersom den måste cachelagras indata för ReadOnlySpan<byte>, som i sig är en referens struct. Dessutom är typen Utf8JsonReader föränderlig eftersom den innehåller tillstånd. Skicka det därför med referens i stället för efter värde. Om det skickas efter värde skulle det resultera i en struct-kopia och tillståndsändringarna skulle inte vara synliga för anroparen.

Mer information om hur du använder referensstrukturer finns i Undvik allokeringar.

Läsa UTF-8-text

För att uppnå bästa möjliga prestanda när du använder läser Utf8JsonReaderdu JSON-nyttolaster som redan kodas som UTF-8-text i stället för UTF-16-strängar. Ett kodexempel finns i Filtrera data med Utf8JsonReader.

Läsa med ReadOnlySequence i flera segment

Om JSON-indata är en ReadOnlySpan-byte<> kan varje JSON-element nås från ValueSpan egenskapen på läsaren när du går igenom läsloopen. Men om dina indata är en ReadOnlySequence-byte<> (vilket är resultatet av läsning från en PipeReader), kan vissa JSON-element korsa flera segment av ReadOnlySequence<byte> objektet. Dessa element skulle inte vara tillgängliga från ValueSpan i ett sammanhängande minnesblock. När du har ett flera segment ReadOnlySequence<byte> som indata avsöker HasValueSequence du i stället egenskapen på läsaren för att ta reda på hur du kommer åt det aktuella JSON-elementet. Här är ett rekommenderat mönster:

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

Använda ValueTextEquals för uppslag för egenskapsnamn

Använd inte ValueSpan för att göra byte-för-byte-jämförelser genom att anropa SequenceEqual efter uppslag för egenskapsnamn. Anropa ValueTextEquals i stället eftersom den metoden tar bort alla tecken som är undantagna i JSON. Här är ett exempel som visar hur du söker efter en egenskap med namnet "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;
    }
}

Läsa null-värden i null-värdetyper

De inbyggda System.Text.Json API:erna returnerar endast icke-nullbara värdetyper. Returnerar Utf8JsonReader.GetBoolean till exempel en bool. Det genererar ett undantag om det hittar Null i JSON. I följande exempel visas två sätt att hantera nullvärden, ett genom att returnera en nullbar värdetyp och ett genom att returnera standardvärdet:

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

Hoppa över underordnade token

Använd metoden Utf8JsonReader.Skip() för att hoppa över underordnade till den aktuella JSON-token. Om tokentypen är JsonTokenType.PropertyNameflyttas läsaren till egenskapsvärdet. Följande kodfragment visar ett exempel på hur du använder Utf8JsonReader.Skip() för att flytta läsaren till värdet för en egenskap.

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

Använda avkodade JSON-strängar

Från och med .NET 7 kan du använda Utf8JsonReader.CopyString metoden i stället för att använda en avkodad Utf8JsonReader.GetString() JSON-sträng. Till skillnad från GetString(), som alltid allokerar en ny sträng, CopyString kan du kopiera den ej kapslade strängen till en buffert som du äger. Följande kodfragment visar ett exempel på hur du använder en UTF-16-sträng med .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)
{
    // ...
}

Se även