System.Text.Json で Utf8JsonWriter を使う方法

この記事では、カスタム シリアライザーを構築するために Utf8JsonWriter 型を使う方法について説明します。

Utf8JsonWriter は、StringInt32DateTime のような一般的な .NET 型から UTF-8 でエンコードされた JSON テキストを書き込むための、ハイパフォーマンスな方法です。 ライターは低レベルの型であり、カスタム シリアライザーを構築するために使用できます。 JsonSerializer.Serialize メソッドでは、内部で Utf8JsonWriter が使用されます。

Utf8JsonWriter クラスを使用する方法を示す例を次に示します。

var options = new JsonWriterOptions
{
    Indented = true
};

using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, options);

writer.WriteStartObject();
writer.WriteString("date", DateTimeOffset.UtcNow);
writer.WriteNumber("temp", 42);
writer.WriteEndObject();
writer.Flush();

string json = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(json);
Dim options As JsonWriterOptions = New JsonWriterOptions With {
    .Indented = True
}

Dim stream As MemoryStream = New MemoryStream
Dim writer As Utf8JsonWriter = New Utf8JsonWriter(stream, options)

writer.WriteStartObject()
writer.WriteString("date", DateTimeOffset.UtcNow)
writer.WriteNumber("temp", 42)
writer.WriteEndObject()
writer.Flush()

Dim json As String = Encoding.UTF8.GetString(stream.ToArray())
Console.WriteLine(json)

UTF-8 テキストで書き込む

Utf8JsonWriter を使用しているときに最大限のパフォーマンスを達成するには、UTF-16 文字列としてではなく、UTF-8 テキストとして既にエンコードされている JSON ペイロードを書き込みます。 UTF-16 文字列リテラルを使用するのではなく、JsonEncodedText を使用して、既知の文字列プロパティの名前と値をスタティックとしてキャッシュおよび事前エンコードし、ライターに渡します。 これは、UTF-8 バイト配列をキャッシュして使用するより高速です。

この方法は、カスタム エスケープ処理を行う必要がある場合にも機能します。 System.Text.Json では、文字列の記述中にエスケープ処理を無効にすることはできません。 ただし、独自のカスタム JavaScriptEncoder をオプションとしてライターに渡すことができます。または、JavascriptEncoder を使用する独自の JsonEncodedText を作成してエスケープ処理を行ってから、文字列の代わりに JsonEncodedText を書き込むこともできます。 詳細については、「文字エンコードをカスタマイズする」を参照してください。

生 JSON の書き込み

一部のシナリオでは、Utf8JsonWriter を使用して作成している JSON ペイロードに "未加工の" JSON を書き込む場合があります。 これは Utf8JsonWriter.WriteRawValue を使用して行うことができます。 典型的なシナリオは次のようになります。

  • 新しい JSON で囲む既存の JSON ペイロードがあります。

  • Utf8JsonWriter の既定の書式設定とは異なる方法で値の書式を設定します。

    たとえば、数値の書式設定をカスタマイズします。 既定では、System.Text.Json では全数の小数点が省略されます。たとえば、1.0 ではなく 1 と書きます。 その理由は、書き込むバイト数が少なければ少ないほどパフォーマンスが向上することにあります。 ただし、JSON を使用する人が小数のある数字を倍精度として、小数のない数字を整数として扱うとします。 そこで、配列内の数字がすべて倍精度として認識されるように、全数には小数点とゼロを書きます。 その方法を次の例に示します。

    using System.Text;
    using System.Text.Json;
    
    namespace WriteRawJson;
    
    public class Program
    {
        public static void Main()
        {
            JsonWriterOptions writerOptions = new() { Indented = true, };
    
            using MemoryStream stream = new();
            using Utf8JsonWriter writer = new(stream, writerOptions);
    
            writer.WriteStartObject();
    
            writer.WriteStartArray("defaultJsonFormatting");
            foreach (double number in new double[] { 50.4, 51 })
            {
                writer.WriteStartObject();
                writer.WritePropertyName("value");
                writer.WriteNumberValue(number);
                writer.WriteEndObject();
            }
            writer.WriteEndArray();
    
            writer.WriteStartArray("customJsonFormatting");
            foreach (double result in new double[] { 50.4, 51 })
            {
                writer.WriteStartObject();
                writer.WritePropertyName("value");
                writer.WriteRawValue(
                    FormatNumberValue(result), skipInputValidation: true);
                writer.WriteEndObject();
            }
            writer.WriteEndArray();
    
            writer.WriteEndObject();
            writer.Flush();
    
            string json = Encoding.UTF8.GetString(stream.ToArray());
            Console.WriteLine(json);
        }
        static string FormatNumberValue(double numberValue)
        {
            return numberValue == Convert.ToInt32(numberValue) ? 
                numberValue.ToString() + ".0" : numberValue.ToString();
        }
    }
    // output:
    //{
    //  "defaultJsonFormatting": [
    //    {
    //      "value": 50.4
    //    },
    //    {
    //      "value": 51
    //    }
    //  ],
    //  "customJsonFormatting": [
    //    {
    //      "value": 50.4
    //    },
    //    {
    //      "value": 51.0
    //    }
    //  ]
    //}
    

文字のエスケープ処理をカスタマイズする

JsonTextWriterStringEscapeHandling 設定には、すべての ASCII 以外の文字または HTML 文字をエスケープするオプションが用意されています。 既定では、Utf8JsonWriter ではすべての ASCII 以外および HTML 文字がエスケープされます。 このエスケープ処理は、多層防御セキュリティ上の理由で行われます。 別のエスケープ処理ポリシーを指定するには、JavaScriptEncoder を作成し、JsonWriterOptions.Encoder を設定します。 詳細については、「文字エンコードをカスタマイズする」を参照してください。

null 値を書き込む

Utf8JsonWriter を使用して null 値を書き込むには、以下を呼び出します。

  • WriteNull。null を値として指定し、キーと値のペアを書き込みます。
  • WriteNullValue。JSON 配列の要素として null を書き込みます。

文字列プロパティでは、文字列が null の場合、WriteStringWriteStringValueWriteNullWriteNullValue に相当します。

Timespan、Uri、または char の値を書き込む

TimespanUri、または char の値を書き込むには、これらを文字列として書式設定し (たとえば ToString() を呼び出します)、WriteStringValue を呼び出します。

こちらもご覧ください