Bagikan melalui


Cara menggunakan Utf8JsonReader di System.Text.Json

Artikel ini memperlihatkan bagaimana Anda dapat menggunakan Utf8JsonReader jenis untuk membangun pengurai dan deserializer kustom.

Utf8JsonReader adalah performa tinggi, alokasi rendah, pembaca khusus penerusan untuk teks JSON yang disandikan UTF-8, dibaca dari ReadOnlySpan<byte> atau ReadOnlySequence<byte>. Utf8JsonReader adalah jenis tingkat rendah yang dapat digunakan untuk membuat pengurai dan deserializer khusus. Metode yang JsonSerializer.Deserialize digunakan Utf8JsonReader di bawah sampul.

Utf8JsonReader tidak dapat digunakan langsung dari kode Visual Basic. Untuk informasi selengkapnya, lihat dukungan (Visual Basic).

Contoh berikut menunjukkan cara menggunakan kelas Utf8JsonReader.

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

Kode sebelumnya mengasumsikan bahwa variabel jsonUtf8 adalah array byte yang berisi JSON yang valid, dikodekan sebagai UTF-8.

Filter data menggunakan Utf8JsonReader

Contoh berikut menunjukkan cara membaca file secara sinkron dan mencari nilai.

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

Untuk versi asinkron dari contoh ini, lihat proyek JSON sampel .NET.

Kode sebelumnya:

  • Mengasumsikan JSON berisi array objek dan setiap objek mungkin berisi properti "nama" dari string jenis.

  • Menghitung objek dan nilai properti "nama" yang diakhiri dengan "University".

  • Mengasumsikan file dikodekan sebagai UTF-16 dan mentranskodekannya ke UTF-8. File yang dikodekan sebagai UTF-8 dapat dibaca langsung ke dalam dengan ReadOnlySpan<byte> menggunakan kode berikut:

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

    Jika file berisi tanda urutan byte (BOM) UTF-8, hapus sebelum meneruskan byte ke Utf8JsonReader, karena pembaca mengharapkan teks. Jika tidak, BOM dianggap JSON tidak valid, dan pembaca memberikan pengecualian.

Berikut adalah sampel JSON yang dapat dibaca kode sebelumnya. Pesan ringkasan yang dihasilkan adalah "2 dari 4 memiliki nama yang berakhiran dengan '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"
  }
]

Membaca dari aliran menggunakan Utf8JsonReader

Saat membaca file besar (ukuran gigabyte atau lebih, misalnya), Anda mungkin ingin menghindari harus memuat seluruh file ke dalam memori sekaligus. Untuk skenario ini, Anda dapat menggunakan FileStream.

Saat menggunakan Utf8JsonReader untuk membaca dari aliran, aturan berikut berlaku:

  • Buffer yang berisi sebagian payload JSON harus setidaknya sebesar token JSON terbesar di dalamnya sehingga pembaca dapat membuat kemajuan.
  • Buffer harus setidaknya sebesar urutan ruang kosong terbesar dalam JSON.
  • Pembaca tidak melacak data yang telah dibacanya hingga benar-benar membaca TokenType berikutnya dalam payload JSON. Jadi ketika ada byte yang tersisa di buffer, Anda harus meneruskannya ke pembaca lagi. Anda dapat menggunakan BytesConsumed untuk menentukan berapa banyak byte yang tersisa.

Kode berikut mengilustrasikan cara membaca dari aliran. Contoh menunjukkan MemoryStream. Kode serupa akan berfungsi dengan FileStream, kecuali ketika FileStream berisi UTF-8 BOM di awal. Dalam hal ini, Anda perlu menanggalkan tiga byte tersebut dari buffer sebelum meneruskan byte yang tersisa ke Utf8JsonReader. Jika tidak, pembaca akan memberikan pengecualian, karena BOM tidak dianggap sebagai bagian yang valid dari JSON.

Kode sampel dimulai dengan buffer 4 KB dan menggandakan ukuran buffer setiap kali menemukan bahwa ukurannya tidak cukup besar untuk menyesuaikan token JSON lengkap, yang diperlukan bagi pembaca untuk membuat kemajuan ke depan pada payload JSON. Sampel JSON yang disediakan dalam cuplikan memicu peningkatan ukuran buffer hanya jika Anda mengatur ukuran buffer awal yang sangat kecil, misalnya, 10 byte. Jika Anda menyetel ukuran buffer awal ke 10, pernyataan Console.WriteLine menggambarkan sebab dan akibat dari peningkatan ukuran buffer. Pada ukuran buffer awal 4 KB, seluruh sampel JSON ditunjukkan oleh masing-masing Console.WriteLine, dan ukuran buffer tidak perlu ditingkatkan.

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

Contoh sebelumnya tidak menetapkan batasan seberapa besar buffer dapat tumbuh. Jika ukuran token terlalu besar, kode bisa gagal dengan pengecualian OutOfMemoryException. Hal ini dapat terjadi jika JSON berisi token berukuran sekitar 1 GB atau lebih, karena menggandakan ukuran 1 GB menghasilkan ukuran yang terlalu besar untuk dimasukkan ke dalam buffer int32.

batasan struktur ref

Karena tipe Utf8JsonReader adalah ref struct, tipe ini memiliki batasan tertentu. Misalnya, itu tidak dapat disimpan sebagai bidang pada kelas atau struktur selain ref struct.

Untuk mencapai performa tinggi, jenis ini harus berupa ref struct karena perlu men-cache input ReadOnlySpan<byte>, yang merupakan ref struct. Selain itu, Utf8JsonReader jenisnya dapat diubah karena memegang status . Oleh karena itu, teruskan dengan referensi bukan berdasarkan nilai. Meneruskannya dengan nilai akan menghasilkan salinan struct dan perubahan status tidak akan terlihat oleh pemanggil.

Untuk informasi selengkapnya tentang cara menggunakan struktur ref, lihat Menghindari alokasi.

Membaca teks UTF-8

Untuk mencapai performa terbaik saat menggunakan Utf8JsonReader, baca payload JSON yang sudah dikodekan sebagai teks UTF-8 daripada sebagai string UTF-16. Untuk contoh kode, lihat Memfilter data menggunakan Utf8JsonReader.

Baca dengan ReadOnlySequence multisegmen

Jika masukan JSON Anda adalah ReadOnlySpan<byte>, setiap elemen JSON dapat diakses dari properti ValueSpan pada pembaca saat Anda melewati loop baca. Namun, jika masukan Anda adalah ReadOnlySequence<byte> (yang merupakan hasil pembacaan dari PipeReader), beberapa elemen JSON mungkin menjangkau beberapa segmen objek ReadOnlySequence<byte>. Elemen-elemen ini tidak akan dapat diakses dari ValueSpan dalam blok memori yang berseberangan. Sebaliknya, setiap kali Anda memiliki multisegmen ReadOnlySequence<byte> sebagai input, polling properti HasValueSequence pada pembaca untuk mencari tahu cara mengakses elemen JSON saat ini. Berikut adalah pola yang direkomendasikan:

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

Gunakan ValueTextEquals untuk pencarian nama properti

Jangan gunakan ValueSpan untuk melakukan perbandingan byte demi byte dengan memanggil SequenceEqual untuk pencarian nama properti. Panggil ValueTextEquals sebagai gantinya, karena metode tersebut membuka semua karakter yang lolos di JSON. Berikut adalah contoh yang menunjukkan cara mencari properti bernama "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;
    }
}

Membaca nilai null ke dalam jenis nilai nullable

API System.Text.Json bawaan hanya mengembalikan jenis nilai yang tidak dapat diubah ke null. Misalnya, Utf8JsonReader.GetBoolean mengembalikan bool. Ini menampilkan pengecualian jika ditemukan Null di JSON. Contoh berikut menunjukkan dua cara untuk menangani null, satu dengan mengembalikan jenis nilai yang dapat diubah ke null dan satu dengan mengembalikan nilai default:

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

Lewati anak-anak token

Utf8JsonReader.Skip() Gunakan metode untuk melewati turunan token JSON saat ini. Jika jenis token adalah JsonTokenType.PropertyName, pembaca berpindah ke nilai properti. Cuplikan kode berikut menunjukkan contoh penggunaan Utf8JsonReader.Skip() untuk memindahkan pembaca ke nilai properti.

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

Mengonsumsi string JSON yang didekodekan

Mulai dari .NET 7, Anda dapat menggunakan metode alih-alih Utf8JsonReader.CopyStringUtf8JsonReader.GetString() menggunakan string JSON yang didekodekan. Tidak seperti GetString(), yang selalu mengalokasikan string baru, CopyString memungkinkan Anda menyalin unescaped string ke buffer yang Anda miliki. Cuplikan kode berikut menunjukkan contoh penggunaan string UTF-16 menggunakan 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)
{
    // ...
}

Lihat juga