Newtonsoft.Json から System.Text.Json に移行する方法

この記事では、Newtonsoft.Json から System.Text.Json に移行する方法を示します。

System.Text.Json 名前空間は、JavaScript Object Notation (JSON) との間でのシリアル化と逆シリアル化の機能を提供します。 System.Text.Json ライブラリは、.NET Core 3.1 以降のバージョン用のランタイムに含まれています。 その他のターゲット フレームワークの場合は、System.Text.Json NuGet パッケージをインストールします。 このパッケージで以下がサポートされます。

  • .NET Standard 2.0 以降のバージョン
  • .NET Framework 4.7.2 以降のバージョン
  • .NET Core 2.0、2.1、および 2.2

System.Text.Json は、主にパフォーマンス、セキュリティ、標準への準拠に焦点を当てています。 これには既定の動作について重要な違いがいくつかあり、Newtonsoft.Json との機能パリティを持つことがこの目的ではありません。 一部のシナリオでは、現在 System.Text.Json に組み込み機能がなくても、推奨される回避策があります。 それ以外のシナリオでは、回避策は実用的ではありません。

最もよく要求される機能を追加する作業が行われています。 お使いのアプリケーションで、欠如している機能を利用する必要がある場合は、シナリオのサポートを追加できるかどうかを確認するために、dotnet/runtime GitHub リポジトリでイシューを提出することを検討してください。 既に計画されている内容については、エピック イシュー #43620 を参照してください。

この記事のほとんどの内容は、JsonSerializer API の使用方法に関するものですが、JsonDocument (ドキュメント オブジェクト モデル: DOM を表します)、Utf8JsonReaderUtf8JsonWriter の型の使用方法に関するガイダンスも含まれています。

Visual Basic では Utf8JsonReader を使用できません。すなわち、カスタム コンバーターも記述できません。 ここで提示する回避策の多くでは、カスタム コンバーターを記述する必要があります。 C# でカスタム コンバーターを記述し、それを Visual Basic プロジェクトに登録できます。 詳細については、「Visual Basic のサポート」をご覧ください。

Newtonsoft.Json と System.Text.Json の相違点の表

次の表は、Newtonsoft.Json の機能と、それと同等の System.Text.Json の機能を一覧にしたものです。 同等機能は次のカテゴリに分けられます。

  • 組み込み機能によってサポートされています。 System.Text.Json で同様の動作を得るには、属性またはグローバル オプションの使用が必要になる場合があります。
  • サポートされていませんが、回避策を適用できます。 回避策とはカスタム コンバーターのことですが、これによって Newtonsoft.Json の機能との完全なパリティが得られるとは限りません。 これらの一部については、サンプル コードが例として提供されます。 Newtonsoft.Json のこれらの機能を利用する必要がある場合、移行するには、.NET オブジェクト モデルに対する変更やその他のコード変更が必要になります。
  • サポートされていません。回避策は実用的でないか不可能です。 Newtonsoft.Json のこれらの機能を利用する必要がある場合、移行を可能にするには大幅な変更が必要です。
Newtonsoft.Json の機能 System.Text.Json での同等機能
既定での大文字と小文字の区別のない逆シリアル化 ✔️ PropertyNameCaseInsensitive グローバル設定
キャメルケースのプロパティ名 ✔️ PropertyNamingPolicy グローバル設定
最小限の文字のエスケープ ✔️ 厳密な文字エスケープ、構成可能
NullValueHandling.Ignore グローバル設定 ✔️ DefaultIgnoreCondition グローバル オプション
コメントを許可する ✔️ ReadCommentHandling グローバル設定
末尾のコンマを許可する ✔️ AllowTrailingCommas グローバル設定
カスタム コンバーターの登録 ✔️ 優先順位の順序が異なる
既定では最大深度がない ✔️ 既定の最大深度は 64、構成可能
PreserveReferencesHandling グローバル設定 ✔️ ReferenceHandling グローバル設定
引用符で囲まれた数値をシリアル化または逆シリアル化する ✔️ NumberHandling グローバル設定、[JsonNumberHandling] 属性
不変のクラスと構造体への逆シリアル化 ✔️ JsonConstructor、C# 9 のレコード
フィールドのサポート ✔️ IncludeFields グローバル設定、[JsonInclude] 属性
DefaultValueHandling グローバル設定 ✔️ DefaultIgnoreCondition グローバル設定
[JsonProperty] での NullValueHandling の設定 ✔️ JsonIgnore 属性
[JsonProperty] での DefaultValueHandling の設定 ✔️ JsonIgnore 属性
文字列以外のキーを含む Dictionary を逆シリアル化する ✔️ Supported
非パブリック プロパティのセッターとゲッターのサポート ✔️ JsonInclude 属性
[JsonConstructor] 属性 ✔️ [JsonConstructor] 属性
ReferenceLoopHandling グローバル設定 ✔️ ReferenceHandling グローバル設定
さまざまな型のサポート ⚠️ 一部の型にはカスタム コンバーターが必要である
ポリモーフィックなシリアル化 ⚠️ サポートされていない、回避策、サンプル
ポリモーフィックな逆シリアル化 ⚠️ サポートされていない、回避策、サンプル
推論された型を object のプロパティに逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
JSON の null リテラルを null 非許容値の型に逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
[JsonProperty] 属性での Required の設定 ⚠️ サポートされていない、回避策、サンプル
プロパティを無視するための DefaultContractResolver ⚠️ サポートされていない、回避策、サンプル
DateTimeZoneHandlingDateFormatString の設定 ⚠️ サポートされていない、回避策、サンプル
コールバック ⚠️ サポートされていない、回避策、サンプル
JsonConvert.PopulateObject メソッド ⚠️ サポートされていない、回避策
ObjectCreationHandling グローバル設定 ⚠️ サポートされていない、回避策
セッターなしでコレクションに追加する ⚠️ サポートされていない、回避策
System.Runtime.Serialization 属性のサポート サポートされていない
MissingMemberHandling グローバル設定 サポートされていない
引用符なしのプロパティ名を許可する サポートされていない
文字列値を囲む単一引用符を許可する サポートされていない
文字列のプロパティに対して文字列ではない JSON 値を許可する サポートされていない
TypeNameHandling.All グローバル設定 サポートされていない
Newtonsoft.Json の機能 System.Text.Json での同等機能
既定での大文字と小文字の区別のない逆シリアル化 ✔️ PropertyNameCaseInsensitive グローバル設定
キャメルケースのプロパティ名 ✔️ PropertyNamingPolicy グローバル設定
最小限の文字のエスケープ ✔️ 厳密な文字エスケープ、構成可能
NullValueHandling.Ignore グローバル設定 ✔️ DefaultIgnoreCondition グローバル オプション
コメントを許可する ✔️ ReadCommentHandling グローバル設定
末尾のコンマを許可する ✔️ AllowTrailingCommas グローバル設定
カスタム コンバーターの登録 ✔️ 優先順位の順序が異なる
既定では最大深度がない ✔️ 既定の最大深度は 64、構成可能
PreserveReferencesHandling グローバル設定 ✔️ ReferenceHandling グローバル設定
引用符で囲まれた数値をシリアル化または逆シリアル化する ✔️ NumberHandling グローバル設定、[JsonNumberHandling] 属性
不変のクラスと構造体への逆シリアル化 ✔️ JsonConstructor、C# 9 のレコード
フィールドのサポート ✔️ IncludeFields グローバル設定、[JsonInclude] 属性
DefaultValueHandling グローバル設定 ✔️ DefaultIgnoreCondition グローバル設定
[JsonProperty] での NullValueHandling の設定 ✔️ JsonIgnore 属性
[JsonProperty] での DefaultValueHandling の設定 ✔️ JsonIgnore 属性
文字列以外のキーを含む Dictionary を逆シリアル化する ✔️ Supported
非パブリック プロパティのセッターとゲッターのサポート ✔️ JsonInclude 属性
[JsonConstructor] 属性 ✔️ [JsonConstructor] 属性
さまざまな型のサポート ⚠️ 一部の型にはカスタム コンバーターが必要である
ポリモーフィックなシリアル化 ⚠️ サポートされていない、回避策、サンプル
ポリモーフィックな逆シリアル化 ⚠️ サポートされていない、回避策、サンプル
推論された型を object のプロパティに逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
JSON の null リテラルを null 非許容値の型に逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
[JsonProperty] 属性での Required の設定 ⚠️ サポートされていない、回避策、サンプル
プロパティを無視するための DefaultContractResolver ⚠️ サポートされていない、回避策、サンプル
DateTimeZoneHandlingDateFormatString の設定 ⚠️ サポートされていない、回避策、サンプル
コールバック ⚠️ サポートされていない、回避策、サンプル
JsonConvert.PopulateObject メソッド ⚠️ サポートされていない、回避策
ObjectCreationHandling グローバル設定 ⚠️ サポートされていない、回避策
セッターなしでコレクションに追加する ⚠️ サポートされていない、回避策
ReferenceLoopHandling グローバル設定 サポートされていない
System.Runtime.Serialization 属性のサポート サポートされていない
MissingMemberHandling グローバル設定 サポートされていない
引用符なしのプロパティ名を許可する サポートされていない
文字列値を囲む単一引用符を許可する サポートされていない
文字列のプロパティに対して文字列ではない JSON 値を許可する サポートされていない
TypeNameHandling.All グローバル設定 サポートされていない
Newtonsoft.Json の機能 System.Text.Json での同等機能
既定での大文字と小文字の区別のない逆シリアル化 ✔️ PropertyNameCaseInsensitive グローバル設定
キャメルケースのプロパティ名 ✔️ PropertyNamingPolicy グローバル設定
最小限の文字のエスケープ ✔️ 厳密な文字エスケープ、構成可能
NullValueHandling.Ignore グローバル設定 ✔️ IgnoreNullValues グローバル オプション
コメントを許可する ✔️ ReadCommentHandling グローバル設定
末尾のコンマを許可する ✔️ AllowTrailingCommas グローバル設定
カスタム コンバーターの登録 ✔️ 優先順位の順序が異なる
既定では最大深度がない ✔️ 既定の最大深度は 64、構成可能
さまざまな型のサポート ⚠️ 一部の型にはカスタム コンバーターが必要である
文字列を数値として逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
文字列以外のキーを含む Dictionary を逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
ポリモーフィックなシリアル化 ⚠️ サポートされていない、回避策、サンプル
ポリモーフィックな逆シリアル化 ⚠️ サポートされていない、回避策、サンプル
推論された型を object のプロパティに逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
JSON の null リテラルを null 非許容値の型に逆シリアル化する ⚠️ サポートされていない、回避策、サンプル
不変のクラスと構造体への逆シリアル化 ⚠️ サポートされていない、回避策、サンプル
[JsonConstructor] 属性 ⚠️ サポートされていない、回避策、サンプル
[JsonProperty] 属性での Required の設定 ⚠️ サポートされていない、回避策、サンプル
[JsonProperty] 属性での NullValueHandling の設定 ⚠️ サポートされていない、回避策、サンプル
[JsonProperty] 属性での DefaultValueHandling の設定 ⚠️ サポートされていない、回避策、サンプル
DefaultValueHandling グローバル設定 ⚠️ サポートされていない、回避策、サンプル
プロパティを無視するための DefaultContractResolver ⚠️ サポートされていない、回避策、サンプル
DateTimeZoneHandlingDateFormatString の設定 ⚠️ サポートされていない、回避策、サンプル
コールバック ⚠️ サポートされていない、回避策、サンプル
パブリックおよび非パブリック フィールドのサポート ⚠️ サポートされていない、回避策
非パブリック プロパティのセッターとゲッターのサポート ⚠️ サポートされていない、回避策
JsonConvert.PopulateObject メソッド ⚠️ サポートされていない、回避策
ObjectCreationHandling グローバル設定 ⚠️ サポートされていない、回避策
セッターなしでコレクションに追加する ⚠️ サポートされていない、回避策
PreserveReferencesHandling グローバル設定 サポートされていない
ReferenceLoopHandling グローバル設定 サポートされていない
System.Runtime.Serialization 属性のサポート サポートされていない
MissingMemberHandling グローバル設定 サポートされていない
引用符なしのプロパティ名を許可する サポートされていない
文字列値を囲む単一引用符を許可する サポートされていない
文字列のプロパティに対して文字列ではない JSON 値を許可する サポートされていない
TypeNameHandling.All グローバル設定 サポートされていない

これは Newtonsoft.Json 機能の包括的なリストではありません。 この一覧には、GitHub の問題または StackOverflow の投稿でリクエストされたシナリオの多くが含まれています。 ここに一覧表示されたシナリオのうち、現在サンプル コードがないシナリオの 1 つに回避策を実装する場合、およびご自分の解決策を共有する場合は、このページの下部にある [フィードバック] セクションで [このページ] を選択してください。 これにより、問題がこのドキュメントの GitHub リポジトリに作成され、このページの [フィードバック] セクションにも記載されます。

Newtonsoft.Json と比較した既定の JsonSerializer の動作の相違点

既定では System.Text.Json は厳格であり、確定的な動作を重視して、呼び出し元のために推測や解釈は行われません。 このライブラリは、パフォーマンスとセキュリティを確保するために意図的にこのように設計されています。 Newtonsoft.Json は既定で柔軟性を持っています。 設計上のこの基本的な違いが、既定の動作における次のような固有の相違点の多くで、その背景にあります。

大文字と小文字の区別のない逆シリアル化

逆シリアル化中、Newtonsoft.Json では、大文字と小文字の区別のないプロパティ名の照合が既定で行われます。 System.Text.Json の既定では大文字と小文字が区別されます。完全一致が行われるため、これによってパフォーマンスが向上します。 大文字と小文字の区別のない照合の実行方法については、「大文字と小文字を区別しないプロパティ照合」を参照してください。

ASP.NET Core を使用して System.Text.Json を間接的に使用している場合、Newtonsoft.Json のような動作を得るために何かをする必要はありません。 ASP.NET Core では、System.Text.Json を使用するときに、Camel 形式のプロパティ名および大文字と小文字を区別しない照合のための設定が指定されます。

ASP.NET Core を使用すると、引用符で囲まれた数値を既定で逆シリアル化することもできます。

最小限の文字のエスケープ

シリアル化中、Newtonsoft.Json では文字をエスケープしないままにすることが比較的許容されています。 つまり、xxxx が文字のコード ポイントである場合、その文字が \uxxxx には置き換えられません。 文字がエスケープされる場合、その文字の前に \ が出力されます (たとえば、"\" になります)。 System.Text.Json の場合、既定でより多くの文字がエスケープされ、クロスサイト スクリプティング (XSS) や情報漏えいの攻撃に対する多層防御保護が実現します。そのために 6 文字のシーケンスが使用されます。 System.Text.Json では、既定で ASCII 以外の文字がすべてエスケープされるため、Newtonsoft.JsonStringEscapeHandling.EscapeNonAscii を使用している場合は、何もする必要はありません。 また、System.Text.Json では、既定で HTML に影響する文字もエスケープされます。 System.Text.Json の既定の動作をオーバーライドする方法については、「文字エンコードをカスタマイズする」を参照してください。

コメント

逆シリアル化中、Newtonsoft.Json では既定で JSON 内のコメントは無視されます。 System.Text.Json の既定では、コメントに対して例外がスローされます。RFC 8259 仕様にそれが含まれていないためです。 コメントを許可する方法については、「コメントと末尾のコンマを許可する」を参照してください。

末尾のコンマ

逆シリアル化中、Newtonsoft.Json では既定で末尾のコンマは無視されます。 また、複数の末尾のコンマ (たとえば、[{"Color":"Red"},{"Color":"Green"},,]) も無視されます。 System.Text.Json の既定では、末尾のコンマに対して例外がスローされます。RFC 8259 仕様でそれが許可されていないためです。 System.Text.Json で受け入れられるようにする方法については、「コメントと末尾のコンマを許可する」を参照してください。 複数の末尾のコンマが許可されるようにする方法はありません。

コンバーターの登録の優先順位

Newtonsoft.Json でのカスタム コンバーターの登録の優先順位は次のとおりです。

  • プロパティの属性
  • 型の属性
  • Converters コレクション

この順序は、Converters コレクション内のカスタム コンバーターが、型レベルの属性を適用して登録されたコンバーターによってオーバーライドされることを意味します。 これらの登録はどちらも、プロパティ レベルの属性によってオーバーライドされます。

System.Text.Json でのカスタム コンバーターの登録の優先順位は異なります。

  • プロパティの属性
  • Converters コレクション
  • 型の属性

ここでの違いは、Converters コレクション内のカスタム コンバーターによって型レベルの属性がオーバーライドされる点です。 この優先順位の背後にある意図は、実行時の変更によって設計時の選択がオーバーライドされるようにすることです。 優先順位を変更する方法はありません。

カスタム コンバーターの登録の詳細については、「カスタム コンバーターを登録する」を参照してください。

最大の深さ

Newtonsoft.Json の場合、既定では最大の深さの制限はありません。 System.Text.Json の場合、64 という既定の制限があり、JsonSerializerOptions.MaxDepth を設定して構成できます。

ASP.NET Core を使用し、System.Text.Json を間接的に使用している場合、深さ上限の既定値は 32 です。 この既定値はモデル バインドの場合と同じであり、JsonOptions クラスで設定されます。

JSON 文字列 (プロパティ名と文字列値)

逆シリアル化中、Newtonsoft.Json では、二重引用符か単一引用符で囲まれた、または引用符なしのプロパティ名が受け取入れられます。 文字列値は、二重引用符または単一引用符で囲まれたものが受け入れられます。 たとえば、Newtonsoft.Json では次の JSON が受け入れられます。

{
  "name1": "value",
  'name2': "value",
  name3: 'value'
}

System.Text.Json では、二重引用符で囲まれたプロパティ名と文字列値のみが受け入れられます。この形式が RFC 8259 仕様で要求されており、有効な JSON と見なされる唯一の形式であるためです。

値を単一引用符で囲むと、次のメッセージと共に JsonException が発生します。

''' is an invalid start of a value.

文字列プロパティに対する文字列以外の値

Newtonsoft.Json では、文字列型のプロパティへの逆シリアル化の場合、数値やリテラル truefalse など、文字列以外の値が受け入れられます。 Newtonsoft.Json で次のクラスへの逆シリアル化が正常に行われてる JSON の例を、以下に示します。

{
  "String1": 1,
  "String2": true,
  "String3": false
}
public class ExampleClass
{
    public string String1 { get; set; }
    public string String2 { get; set; }
    public string String3 { get; set; }
}

System.Text.Json では、文字列以外の値は文字列プロパティに逆シリアル化されません。 文字列フィールドに対して文字列以外の値を受け取ると、次のメッセージと共に JsonException が発生します。

The JSON value could not be converted to System.String.

JsonSerializer を使用するシナリオ

次のシナリオの一部は、組み込み機能ではサポートされていませんが、回避策を適用できます。 回避策とはカスタム コンバーターのことですが、これによって Newtonsoft.Json の機能との完全なパリティが得られるとは限りません。 これらの一部については、サンプル コードが例として提供されます。 Newtonsoft.Json のこれらの機能を利用する必要がある場合、移行するには、.NET オブジェクト モデルに対する変更やその他のコード変更が必要になります。

次のシナリオの一部では、回避策は実用的でないか不可能です。 Newtonsoft.Json のこれらの機能を利用する必要がある場合、移行を可能にするには大幅な変更が必要です。

引用符で囲まれた数値を許可または記述する

Newtonsoft.Json では、JSON 文字列によって表された (引用符で囲まれた) 数値をシリアル化または逆シリアル化できます。 たとえば、{"DegreesCelsius":23} ではなく {"DegreesCelsius":"23"} を受け入れることができます。 System.Text.Json でその動作を有効にするには、JsonSerializerOptions.NumberHandlingWriteAsString または AllowReadingFromString に設定するか、[JsonNumberHandling] 属性を使用します。

ASP.NET Core を使用して System.Text.Json を間接的に使用している場合、Newtonsoft.Json のような動作を得るために何かをする必要はありません。 System.Text.Json が使用されていて、Web の既定値で引用符で囲まれた数値が許可されている場合、ASP.NET Core により Web の既定値が指定されます。

詳細については、「引用符で囲まれた数値を許可または記述する」を参照してください。

Newtonsoft.Json では、JSON 文字列によって表された (引用符で囲まれた) 数値をシリアル化または逆シリアル化できます。 たとえば、{"DegreesCelsius":23} ではなく {"DegreesCelsius":"23"} を受け入れることができます。 .NET Core 3.1 の System.Text.Json でその動作を有効にするには、次の例のようなカスタム コンバーターを実装します。 このコンバーターでは、long として定義されたプロパティが処理されます。

  • JSON 文字列としてシリアル化されます。
  • 逆シリアル化中に、JSON の数値と引用符で囲まれた数値が受け入れられます。
using System;
using System.Buffers;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class LongToStringConverter : JsonConverter<long>
    {
        public override long Read(
            ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
        {
            if (reader.TokenType == JsonTokenType.String)
            {
                ReadOnlySpan<byte> span =
                    reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;

                if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) &&
                    span.Length == bytesConsumed)
                {
                    return number;
                }

                if (long.TryParse(reader.GetString(), out number))
                {
                    return number;
                }
            }

            return reader.GetInt64();
        }

        public override void Write(
            Utf8JsonWriter writer, long longValue, JsonSerializerOptions options) =>
            writer.WriteStringValue(longValue.ToString());
    }
}

このカスタム コンバーターを登録するには、個々の long プロパティで属性を使用するか、Converters コレクションにコンバーター を追加します。

逆シリアル化のときに使用するコンストラクターを指定する

Newtonsoft.Json[JsonConstructor] 属性を使用すると、POCO への逆シリアル化時に呼び出すコンストラクターを指定できます。

System.Text.Json には、[JsonConstructor] 属性もあります。 詳細については、「不変の型とレコード」を参照してください。

.NET Core 3.1 の System.Text.Json では、パラメーターなしのコンストラクターのみがサポートされます。 回避策として、必要なコンストラクターをカスタム コンバーターで呼び出すことができます。 「不変のクラスと構造体への逆シリアル化」の例を参照してください。

プロパティを条件付きで無視する

Newtonsoft.Json には、シリアル化または逆シリアル化時にプロパティを条件付きで無視する方法がいくつか用意されています。

  • DefaultContractResolver を使用すると、任意の条件に基づいて含めたり無視したりするプロパティを選択できます。
  • JsonSerializerSettingsNullValueHandlingDefaultValueHandling の設定を使用すると、null 値または既定値のすべてのプロパティを無視することを指定できます。
  • [JsonProperty] 属性の NullValueHandlingDefaultValueHandling の設定を使用すると、null または既定値に設定されている場合に無視する個々のプロパティを指定できます。

System.Text.Json には、シリアル化中にプロパティまたはフィールドを無視するために次の方法が用意されています。

.NET Core 3.1 の System.Text.Json には、シリアル化中にプロパティを無視するために次の方法が用意されています。

  • プロパティで [JsonIgnore] 属性を使用すると、シリアル化中にそのプロパティが JSON から除外されます。
  • IgnoreNullValues グローバル オプションを使用すると、すべての null 値プロパティを無視できます。 IgnoreNullValues は .NET 5 以降のバージョンでは非推奨であるため、IntelliSense では表示されません。 null 値を無視する現在の方法については、.NET 5 以降ですべての null 値プロパティを無視する 方法に関するページを参照してください。
  • IgnoreReadOnlyProperties グローバル オプションを使用すると、すべての読み取り専用プロパティを無視できます。

これらのオプションでは、以下を行うことは できません

  • 実行時に評価される任意の基準に基づいて、選択したプロパティを無視する。
  • 型に対して既定値を持つすべてのプロパティを無視する。
  • 型に対して既定値を持つ、選択したプロパティを無視する。
  • 選択したプロパティを、値が null である場合に無視する。
  • 実行時に評価される任意の基準に基づいて、選択したプロパティを無視する。

この機能のために、カスタム コンバーターを記述できます。 この方法を紹介するサンプルの POCO とそれに対するカスタム コンバーターを、次に示します。

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastRuntimeIgnoreConverter : JsonConverter<WeatherForecast>
    {
        public override WeatherForecast Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            }

            var wf = new WeatherForecast();

            while (reader.Read())
            {
                if (reader.TokenType == JsonTokenType.EndObject)
                {
                    return wf;
                }

                if (reader.TokenType == JsonTokenType.PropertyName)
                {
                    string propertyName = reader.GetString();
                    reader.Read();
                    switch (propertyName)
                    {
                        case "Date":
                            DateTimeOffset date = reader.GetDateTimeOffset();
                            wf.Date = date;
                            break;
                        case "TemperatureCelsius":
                            int temperatureCelsius = reader.GetInt32();
                            wf.TemperatureCelsius = temperatureCelsius;
                            break;
                        case "Summary":
                            string summary = reader.GetString();
                            wf.Summary = string.IsNullOrWhiteSpace(summary) ? "N/A" : summary;
                            break;
                    }
                }
            }

            throw new JsonException();
        }

        public override void Write(Utf8JsonWriter writer, WeatherForecast wf, JsonSerializerOptions options)
        {
            writer.WriteStartObject();

            writer.WriteString("Date", wf.Date);
            writer.WriteNumber("TemperatureCelsius", wf.TemperatureCelsius);
            if (!string.IsNullOrWhiteSpace(wf.Summary) && wf.Summary != "N/A")
            {
                writer.WriteString("Summary", wf.Summary);
            }

            writer.WriteEndObject();
        }
    }
}

このコンバーターでは、Summary プロパティは、値が null、空の文字列、または "N/A" の場合、シリアル化から除外されます。

このカスタム コンバーターを登録するには、クラスで属性を使用するか、Converters コレクションにコンバーター を追加します。

この方法では、次の場合に追加のロジックが必要になります。

  • POCO に複合プロパティが含まれている。
  • [JsonIgnore] などの属性や、カスタム エンコーダーなどのオプションを処理する必要がある。

パブリックおよび非パブリック フィールド

Newtonsoft.Json では、フィールドおよびプロパティをシリアル化および逆シリアル化できます。

System.Text.Json では、シリアル化または逆シリアル化のときにフィールドを含めるには、JsonSerializerOptions.IncludeFields グローバル設定または [JsonInclude] 属性を使用します。 例については、「フィールドを含める」を参照してください。

.NET Core 3.1 の System.Text.Json は、パブリック プロパティでのみ機能します。 この機能は、カスタム コンバーターによって得られます。

オブジェクト参照を保持してループを処理する

既定では、Newtonsoft.Json では値によってシリアル化します。 たとえば、オブジェクトに同じ Person オブジェクトへの参照を含む 2 つのプロパティが含まれている場合、その Person オブジェクトのプロパティの値は JSON 内で複製されます。

Newtonsoft.Json には、JsonSerializerSettings に、参照によってシリアル化できるようにする PreserveReferencesHandling 設定があります。

  • 最初の Person オブジェクト用に作成された JSON に識別子メタデータが追加されます。
  • 2番目の Person オブジェクト用に作成された JSON には、プロパティ値ではなく、その識別子への参照が含まれます。

Newtonsoft.Json には、例外をスローするのではなく循環参照を無視できるようにする ReferenceLoopHandling 設定もあります。

System.Text.Json で参照を保持し、循環参照を処理するには、JsonSerializerOptions.ReferenceHandlerPreserve に設定します。 ReferenceHandler.Preserve 設定は、Newtonsoft.Json での PreserveReferencesHandling = PreserveReferencesHandling.All に相当します。

ReferenceHandler.IgnoreCycles オプションは、Newtonsoft.Json ReferenceLoopHandling.Ignore と同じように動作します。 1 つの違いは、System.Text.Json の実装では、参照ループが、オブジェクト参照を無視するのではなく、null JSON トークンに置き換えられている点です。 詳細については、「循環参照を無視する」を参照してください。

Newtonsoft.Json の ReferenceResolver のように、シリアル化および逆シリアル化で参照を維持するための動作は、System.Text.Json.Serialization.ReferenceResolver クラスによって定義されます。 カスタム動作を指定するには、派生クラスを作成します。 例については、GuidReferenceResolver に関するページを参照してください。

一部の関連する Newtonsoft.Json 機能はサポートされていません。

.NET Core 3.1 の System.Text.Json でサポートされているのは値によるシリアル化のみであり、循環参照の場合は例外がスローされます。

文字列以外のキーを含むディクショナリ

Newtonsoft.JsonSystem.Text.Json のどちらでも、Dictionary<TKey, TValue> 型のコレクションがサポートされています。 ただし、System.Text.Json では、TKey はカスタムの型ではなく、プリミティブ型にする必要があります。 詳細については、サポートされているキーの型に関するページをご覧ください。

注意事項

TKeystring 以外のものとして型指定される Dictionary<TKey, TValue> に逆シリアル化すると、それを使用するアプリケーションでセキュリティに脆弱性が生じるおそれがあります。 詳細については、「dotnet/runtime#4761」を参照してください。

Newtonsoft.Json では、Dictionary<TKey, TValue> 型のコレクションがサポートされます。 .NET Core 3.1 の System.Text.Json では、ディクショナリ コレクションに対する組み込みサポートは Dictionary<string, TValue> に限定されています。 つまり、キーは文字列である必要があります。

.NET Core 3.1 でキーとして整数やその他の型を含むディクショナリをサポートするには、「カスタム コンバーターを記述する方法」の例にあるようなコンバーターを作成します。

組み込みサポートのない型

System.Text.Json には、次の型に対する組み込みサポートはありません。

カスタム コンバーターは、組み込みサポートのない型に対して実装できます。

ポリモーフィックなシリアル化

Newtonsoft.Json では、ポリモーフィックなシリアル化が自動的に行われます。 System.Text.Json の制限付きのポリモーフィックなシリアル化機能については、「派生クラスのプロパティのシリアル化」を参照してください。

そこで説明されている回避策は、派生クラスを含むことができるプロパティを、object 型として定義することです。 これが不可能な場合、別のオプションとして、カスタム コンバーターを記述する方法に関する記事の例にあるような、継承型階層全体に対する Write メソッドを使用してコンバーターを作成する方法があります。

ポリモーフィックな逆シリアル化

Newtonsoft.Json には、シリアル化中に型名のメタデータを JSON に追加する TypeNameHandling 設定があります。 これは、逆シリアル化中にそのメタデータを使用して、ポリモーフィックな逆シリアル化を行います。 System.Text.Json では限定された範囲のポリモーフィックなシリアル化を行うことができますが、ポリモーフィックな逆シリアル化を行うことはできません。

ポリモーフィックな逆シリアル化をサポートするには、カスタム コンバーターを記述する方法に関する記事の例にあるようなコンバーターを作成します。

オブジェクト プロパティの逆シリアル化

Newtonsoft.Json では、Object への逆シリアル化中に以下が行われます。

  • JSON ペイロード内のプリミティブ値 (null 以外) の型を推測し、格納されている stringlongdoubleboolean、または DateTime をボックス化されたオブジェクトとして返します。 "プリミティブ値" は、JSON の数値、文字列、truefalsenull などの 1 つの JSON 値です。
  • JSON ペイロード内の複合値の JObject または JArray を返します。 "複合値" は、中かっこ ({}) 内の JSON のキーと値のペアのコレクション、または角かっこ ([]) 内の値のリストです。 中かっこまたは角かっこ内のプロパティと値には、追加のプロパティまたは値を含めることができます。
  • ペイロードに null JSON リテラルが含まれている場合は、null 参照を返します。

System.Text.Json では、Object への逆シリアル化中には常に、プリミティブと複合の両方の値に対してボックス化された JsonElement が格納されます。例えば、以下のようなものです。

  • object プロパティ。
  • object ディクショナリ値。
  • object 配列値。
  • ルート object

ただし、System.Text.Json では nullNewtonsoft.Json と同じ方法で処理され、ペイロードに null JSON リテラルが含まれている場合は null 参照が返されます。

object プロパティに型の推定を実装するには、カスタム コンバーターを記述する方法に関する記事の例にあるようなコンバーターを作成します。

null を null 非許容型に逆シリアル化する

Newtonsoft.Json では、次のシナリオでは例外がスローされません。

  • NullValueHandlingIgnore に設定されている。さらに
  • 逆シリアル化中に、JSON の null 非許容値型に null 値が含まれている。

System.Text.Json では、同じシナリオで例外がスローされます。 (System.Text.Json での対応する null 処理設定は JsonSerializerOptions.IgnoreNullValues = true です。)

ターゲット型を所有している場合、最適な回避策は、問題のプロパティを null 許容にすることです (たとえば、intint? に変更します)。

もう 1 つの回避策として、DateTimeOffset 型に対する null 値を処理する次の例のように、型のコンバーターを作成する方法があります。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>
    {
        public override DateTimeOffset Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options) =>
            reader.TokenType == JsonTokenType.Null
                ? default
                : reader.GetDateTimeOffset();

        public override void Write(
            Utf8JsonWriter writer,
            DateTimeOffset dateTimeValue,
            JsonSerializerOptions options) =>
            writer.WriteStringValue(dateTimeValue);
    }
}

このカスタム コンバーターを登録するには、プロパティで属性を使用するか、Converters コレクションにコンバーター を追加します。

注: 上記のコンバーターでは、既定値を指定する POCO に対して Newtonsoft.Json が処理する場合とは 異なる方法で null 値が処理 されます。 たとえば、次のコードがターゲット オブジェクトを表しているとします。

public class WeatherForecastWithDefault
{
    public WeatherForecastWithDefault()
    {
        Date = DateTimeOffset.Parse("2001-01-01");
        Summary = "No summary";
    }
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

さらに、上記のコンバーターを使用して、次の JSON が逆シリアル化されるとします。

{
  "Date": null,
  "TemperatureCelsius": 25,
  "Summary": null
}

逆シリアル化が行われた後、Date プロパティには 1/1/0001 (default(DateTimeOffset)) が設定されます。つまり、コンストラクターで設定された値が上書きされます。 同じ POCO と JSON を使用する場合、Newtonsoft.Json の逆シリアル化では Date プロパティに 1/1/2001 が設定されたままになります。

不変のクラスと構造体への逆シリアル化

Newtonsoft.Json では、パラメーターを持つコンストラクターを使用できるため、不変のクラスと構造体に逆シリアル化できます。

System.Text.Json では、パラメーター化されたコンストラクターの使用を指定するには [JsonConstructor] 属性を使用します。 C# 9 のレコードも不変であり、逆シリアル化ターゲットとしてサポートされています。 詳細については、「不変の型とレコード」を参照してください。

.NET Core 3.1 の System.Text.Json では、パラメーターなしのパブリック コンストラクターのみがサポートされます。 回避策として、カスタム コンバーターでパラメーターを持つコンストラクターを呼び出すことができます。

複数のコンストラクター パラメーターを持つ不変の構造体を次に示します。

public readonly struct ImmutablePoint
{
    public ImmutablePoint(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; }
    public int Y { get; }
}

さらに、この構造体をシリアル化および逆シリアル化するコンバーターを示します。

using System;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
    {
        private readonly JsonEncodedText _xName = JsonEncodedText.Encode("X");
        private readonly JsonEncodedText _yName = JsonEncodedText.Encode("Y");

        private readonly JsonConverter<int> _intConverter;

        public ImmutablePointConverter(JsonSerializerOptions options) => 
            _intConverter = options?.GetConverter(typeof(int)) is JsonConverter<int> intConverter
                ? intConverter
                : throw new InvalidOperationException();

        public override ImmutablePoint Read(
            ref Utf8JsonReader reader,
            Type typeToConvert,
            JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.StartObject)
            {
                throw new JsonException();
            };

            int? x = default;
            int? y = default;

            // Get the first property.
            reader.Read();
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            if (reader.ValueTextEquals(_xName.EncodedUtf8Bytes))
            {
                x = ReadProperty(ref reader, options);
            }
            else if (reader.ValueTextEquals(_yName.EncodedUtf8Bytes))
            {
                y = ReadProperty(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }

            // Get the second property.
            reader.Read();
            if (reader.TokenType != JsonTokenType.PropertyName)
            {
                throw new JsonException();
            }

            if (x.HasValue && reader.ValueTextEquals(_yName.EncodedUtf8Bytes))
            {
                y = ReadProperty(ref reader, options);
            }
            else if (y.HasValue && reader.ValueTextEquals(_xName.EncodedUtf8Bytes))
            {
                x = ReadProperty(ref reader, options);
            }
            else
            {
                throw new JsonException();
            }

            reader.Read();

            if (reader.TokenType != JsonTokenType.EndObject)
            {
                throw new JsonException();
            }

            return new ImmutablePoint(x.GetValueOrDefault(), y.GetValueOrDefault());
        }

        private int ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)
        {
            Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);

            reader.Read();
            return _intConverter.Read(ref reader, typeof(int), options);
        }

        private void WriteProperty(Utf8JsonWriter writer, JsonEncodedText name, int intValue, JsonSerializerOptions options)
        {
            writer.WritePropertyName(name);
            _intConverter.Write(writer, intValue, options);
        }

        public override void Write(
            Utf8JsonWriter writer,
            ImmutablePoint point,
            JsonSerializerOptions options)
        {
            writer.WriteStartObject();
            WriteProperty(writer, _xName, point.X, options);
            WriteProperty(writer, _yName, point.Y, options);
            writer.WriteEndObject();
        }
    }
}

このカスタム コンバーターを登録するには、Converters コレクションにコンバーターを追加します。

オープン ジェネリック プロパティを処理する同様のコンバーターの例については、キーと値のペアの組み込みコンバーターに関する記事を参照してください。

必須プロパティ

Newtonsoft.Json では、[JsonProperty] 属性に Required を設定して、プロパティが必須であることを指定します。 必須とマーク付けされたプロパティに対して JSON で値が取得されない場合、Newtonsoft.Json で例外がスローされます。

System.Text.Json では、ターゲット型のいずれかのプロパティに対して値が取得されない場合でも、例外はスローされません。 たとえば、WeatherForecast クラスがあるとします。

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

次の JSON はエラーなしで逆シリアル化されます。

{
    "TemperatureCelsius": 25,
    "Summary": "Hot"
}

JSON に Date プロパティがない場合に逆シリアル化が失敗するようにするには、カスタム コンバーターを実装します。 次のサンプル コンバーター コードでは、逆シリアル化の完了後に Date プロパティが設定されていない場合は例外がスローされます。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastRequiredPropertyConverter : JsonConverter<WeatherForecast>
    {
        public override WeatherForecast Read(
            ref Utf8JsonReader reader,
            Type type,
            JsonSerializerOptions options)
        {
            // Don't pass in options when recursively calling Deserialize.
            WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

            // Check for required fields set by values in JSON
            return forecast.Date == default
                ? throw new JsonException("Required property not received in the JSON")
                : forecast;
        }

        public override void Write(
            Utf8JsonWriter writer,
            WeatherForecast forecast, JsonSerializerOptions options)
        {
            // Don't pass in options when recursively calling Serialize.
            JsonSerializer.Serialize(writer, forecast);
        }
    }
}

このカスタム コンバーターを登録するには、JsonSerializerOptions.Converters コレクションにコンバーターを追加します。

コンバーターを再帰的に呼び出すこのパターンでは、属性を使用するのではなく、JsonSerializerOptions を使用してコンバーターを登録する必要があります。 属性を使用してこのコンバーターを登録する場合、カスタム コンバーターによってそれ自体が再帰的に呼び出されます。 その結果、スタック オーバーフロー例外で終了する無限ループが発生します。

オプション オブジェクトを使用してコンバーターを登録する場合は、Serialize または Deserialize を再帰的に呼び出すときにオプション オブジェクトで渡さないようにして、無限ループを回避します。 オプション オブジェクトには、Converters コレクションが含まれています。 それを Serialize または Deserialize に渡すと、カスタム コンバーターはそれ自体を呼び出し、無限ループが形成され、結果的にスタック オーバーフロー例外が発生します。 既定のオプションを使用できない場合は、必要な設定を使用してオプションの新しいインスタンスを作成します。 この方法では、新しい各インスタンスが個別にキャッシュされるため、速度が遅くなります。

変換されるクラスで JsonConverterAttribute の登録を使用できる代替パターンがあります。 この方法では、変換されるクラスから派生したクラスで、コンバーター コードによって Serialize または Deserialize が呼び出されます。 この派生クラスには、JsonConverterAttribute が適用されていません。 この代替の例を次に示します。

  • WeatherForecastWithRequiredPropertyConverterAttribute は、逆シリアル化されるクラスであり、JsonConverterAttribute が適用されています。
  • WeatherForecastWithoutRequiredPropertyConverterAttribute は、コンバーター属性を持たない派生クラスです。
  • 無限ループを回避するために、コンバーターのコードによって、WeatherForecastWithoutRequiredPropertyConverterAttributeSerializeDeserialize が呼び出されます。 追加のオブジェクトのインスタンス化とプロパティ値のコピーによって、このシリアル化の方法にはパフォーマンス コストが発生します。

次に WeatherForecast* の種類を示します。

[JsonConverter(typeof(WeatherForecastRequiredPropertyConverterForAttributeRegistration))]
public class WeatherForecastWithRequiredPropertyConverterAttribute
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}

public class WeatherForecastWithoutRequiredPropertyConverterAttribute :
    WeatherForecastWithRequiredPropertyConverterAttribute
{
}

コンバーターを次に示します。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastRequiredPropertyConverterForAttributeRegistration :
        JsonConverter<WeatherForecastWithRequiredPropertyConverterAttribute>
    {
        public override WeatherForecastWithRequiredPropertyConverterAttribute Read(
            ref Utf8JsonReader reader,
            Type type,
            JsonSerializerOptions options)
        {
            // OK to pass in options when recursively calling Deserialize.
            WeatherForecastWithRequiredPropertyConverterAttribute forecast =
                JsonSerializer.Deserialize<WeatherForecastWithoutRequiredPropertyConverterAttribute>(
                    ref reader,
                    options);

            // Check for required fields set by values in JSON.
            return forecast.Date == default
                ? throw new JsonException("Required property not received in the JSON")
                : forecast;
        }

        public override void Write(
            Utf8JsonWriter writer,
            WeatherForecastWithRequiredPropertyConverterAttribute forecast,
            JsonSerializerOptions options)
        {
            var weatherForecastWithoutConverterAttributeOnClass =
                new WeatherForecastWithoutRequiredPropertyConverterAttribute
                {
                    Date = forecast.Date,
                    TemperatureCelsius = forecast.TemperatureCelsius,
                    Summary = forecast.Summary
                };

            // OK to pass in options when recursively calling Serialize.
            JsonSerializer.Serialize(
                writer,
                weatherForecastWithoutConverterAttributeOnClass,
                options);
        }
    }
}

属性 ([JsonIgnore] など) または別のオプション (カスタム エンコーダーなど) を処理する必要がある場合、必須プロパティのコンバーターには追加のロジックが必要です。 また、例のコードでは、既定値がコンストラクター内で設定されているプロパティは処理されません。 さらに、この方法では次のシナリオの違いは識別されません。

  • プロパティが JSON から欠落している。
  • null 非許容型のプロパティが JSON に存在するが、値がその型の既定である (たとえば、int ではゼロ)。
  • null 許容値型のプロパティが JSON に存在するが、値が null である。

日付形式を指定する

Newtonsoft.Json には、DateTime および DateTimeOffset 型のプロパティをどのようにシリアル化および逆シリアル化するかを制御する方法がいくつか用意されています。

  • DateTimeZoneHandling 設定を使用すると、すべての DateTime 値を UTC 日付としてシリアル化できます。
  • DateFormatString 設定と DateTime コンバーターを使用すると、日付文字列の形式をカスタマイズできます。

System.Text.Json は、RFC 3339 プロファイルを含む ISO 8601-1:2019 をサポートしています。 この形式は広く採用されており、明確で、正確にラウンドトリップを行います。 他の形式を使用するには、カスタム コンバーターを作成します。 たとえば、次のコンバーターを使用すると、Unix エポックのタイム ゾーン オフセットがある形式とない形式 (/Date(1590863400000-0700)//Date(1590863400000)/ などの値) を使用する JSON がシリアル化および逆シリアル化されます。

sealed class UnixEpochDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
    static readonly DateTimeOffset s_epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
    static readonly Regex s_regex = new Regex("^/Date\\(([+-]*\\d+)([+-])(\\d{2})(\\d{2})\\)/$", RegexOptions.CultureInvariant);

    public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {

        string formatted = reader.GetString();
        Match match = s_regex.Match(formatted);

        if (
                !match.Success
                || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime)
                || !int.TryParse(match.Groups[3].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int hours)
                || !int.TryParse(match.Groups[4].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out int minutes))
        {
            throw new JsonException();
        }

        int sign = match.Groups[2].Value[0] == '+' ? 1 : -1;
        TimeSpan utcOffset = new TimeSpan(hours * sign, minutes * sign, 0);

        return s_epoch.AddMilliseconds(unixTime).ToOffset(utcOffset);
    }

    public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);
        TimeSpan utcOffset = value.Offset;

        string formatted = FormattableString.Invariant($"/Date({unixTime}{(utcOffset >= TimeSpan.Zero ? "+" : "-")}{utcOffset:hhmm})/");
        writer.WriteStringValue(formatted);
    }
}
sealed class UnixEpochDateTimeConverter : JsonConverter<DateTime>
{
    static readonly DateTime s_epoch = new DateTime(1970, 1, 1, 0, 0, 0);
    static readonly Regex s_regex = new Regex("^/Date\\(([+-]*\\d+)\\)/$", RegexOptions.CultureInvariant);

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {

        string formatted = reader.GetString();
        Match match = s_regex.Match(formatted);

        if (
                !match.Success
                || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime))
        {
            throw new JsonException();
        }

        return s_epoch.AddMilliseconds(unixTime);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);

        string formatted = FormattableString.Invariant($"/Date({unixTime})/");
        writer.WriteStringValue(formatted);
    }
}

詳細については、「System.Text.Json での DateTime と DateTimeOffset のサポート」を参照してください。

コールバック

Newtonsoft.Json では、シリアル化または逆シリアル化プロセスの複数の時点でカスタム コードを実行できます。

  • OnDeserializing (オブジェクトの逆シリアル化の開始時点)
  • OnDeserialized (オブジェクトの逆シリアル化の完了時点)
  • OnSerializing (オブジェクトのシリアル化の開始時点)
  • OnSerialized (オブジェクトのシリアル化の完了時点)

System.Text.Json では、カスタム コンバーターを記述することでコールバックをシミュレートできます。 次の例は、POCO のカスタム コンバーターを示しています。 このコンバーターには、Newtonsoft.Json のコールバックに対応する各時点でメッセージを表示するコードが含まれています。

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
    public class WeatherForecastCallbacksConverter : JsonConverter<WeatherForecast>
    {
        public override WeatherForecast Read(
            ref Utf8JsonReader reader,
            Type type,
            JsonSerializerOptions options)
        {
            // Place "before" code here (OnDeserializing),
            // but note that there is no access here to the POCO instance.
            Console.WriteLine("OnDeserializing");

            // Don't pass in options when recursively calling Deserialize.
            WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

            // Place "after" code here (OnDeserialized)
            Console.WriteLine("OnDeserialized");

            return forecast;
        }

        public override void Write(
            Utf8JsonWriter writer,
            WeatherForecast forecast, JsonSerializerOptions options)
        {
            // Place "before" code here (OnSerializing)
            Console.WriteLine("OnSerializing");

            // Don't pass in options when recursively calling Serialize.
            JsonSerializer.Serialize(writer, forecast);

            // Place "after" code here (OnSerialized)
            Console.WriteLine("OnSerialized");
        }
    }
}

このカスタム コンバーターを登録するには、Converters コレクションにコンバーターを追加します。

上記のサンプルに従ったカスタム コンバーターを使用すると、次のようになります。

  • OnDeserializing コードでは、新しい POCO インスタンスにアクセスできません。 逆シリアル化の開始時に新しい POCO インスタンスを操作するには、そのコードを POCO コンストラクターに挿入します。
  • オプション オブジェクトでコンバーターを登録し、Serialize または Deserialize を再帰的に呼び出すときにオプション オブジェクトで渡さないようにして、無限ループを回避します。

Serialize または Deserialize を再帰的に呼び出すカスタム コンバーターの詳細については、この記事の前半にある「必須プロパティ」セクションをご覧ください。

非パブリック プロパティのセッターとゲッター

Newtonsoft.Json では、JsonProperty 属性を通じて、プライベートおよび内部プロパティのセッターとゲッターを使用できます。

System.Text.Json では、[JsonInclude] 属性を通じて、プライベートおよび内部プロパティのセッターとゲッターがサポートされます。 サンプル コードについては、「パブリックでないプロパティ アクセサー」を参照してください。

.NET Core 3.1 の System.Text.Json では、パブリック セッターのみがサポートされています。 この機能は、カスタム コンバーターによって得られます。

既存のオブジェクトの設定

Newtonsoft.JsonJsonConvert.PopulateObject メソッドでは、新しいインスタンスを作成するのではなく、クラスの既存のインスタンスに JSON ドキュメントが逆シリアル化されます。 System.Text.Json では常に、既定のパラメーターなしのパブリック コンストラクターを使用して、ターゲット型の新しいインスタンスが作成されます。 カスタム コンバーターによって、既存のインスタンスに逆シリアル化できます。

プロパティを置き換えるのではなく再利用する

Newtonsoft.JsonObjectCreationHandling 設定を使用すると、逆シリアル化中に、プロパティ内のオブジェクトを置き換えるのではなく再利用することを指定できます。 System.Text.Json では常に、プロパティ内のオブジェクトが置き換えられます。 この機能は、カスタム コンバーターによって得られます。

セッターなしでコレクションに追加する

逆シリアル化中、Newtonsoft.Json ではプロパティにセッターがない場合でも、オブジェクトがコレクションに追加されます。 System.Text.Json では、セッターのないプロパティは無視されます。 この機能は、カスタム コンバーターによって得られます。

System.Runtime.Serialization 属性

System.Text.Json では、DataMemberAttributeIgnoreDataMemberAttribute などの、System.Runtime.Serialization 名前空間の属性はサポートされません。

8 進数

Newtonsoft.Json では、先頭にゼロを持つ数値は 8 進数として扱われます。 System.Text.Json では先頭のゼロは許容されません。RFC 8259 仕様で許可されていないためです。

MissingMemberHandling

Newtonsoft.Json は、ターゲット型に存在しないプロパティが JSON に含まれている場合、逆シリアル化中に例外をスローするように構成できます。 System.Text.Json では、[JsonExtensionData] 属性を使用する場合を除き、JSON 内の余分なプロパティは無視されます。 欠落しているメンバー機能に対する回避策はありません。

TraceWriter

Newtonsoft.Json では、TraceWriter を使用してシリアル化または逆シリアル化によって生成されたログを表示し、デバッグできます。 System.Text.Json では、ログ記録は行われません。

JToken (JObject、JArray など) と比較した JsonDocument と JsonElement

System.Text.Json.JsonDocument には、既存の JSON ペイロードから 読み取り専用 のドキュメント オブジェクト モデル (DOM) を解析して作成する機能が用意されています。 DOM では、JSON ペイロード内のデータへのランダム アクセスが提供されます。 ペイロードを構成する JSON 要素には、JsonElement 型を使用してアクセスできます。 JsonElement 型には、JSON テキストを一般的な .NET 型に変換するための API が用意されています。 JsonDocument では RootElement プロパティが公開されます。

変更可能な DOM のサポートに関するドキュメントは開発中です。 追加されるまでは、.NET 6 Preview 4 に関するお知らせをご覧ください。

JsonDocument は IDisposable

JsonDocument では、データのメモリ内ビューがプールされたバッファー内に作成されます。 そのため、Newtonsoft.JsonJObjectJArray とは異なり、JsonDocument 型は IDisposable を実装し、Using ブロック内で使用される必要があります。

有効期間中全体の所有権を呼び出し元に移譲し、責任を破棄する場合は、API から JsonDocument のみを返します。 ほとんどのシナリオでは、これは必要ありません。 呼び出し元が JSON ドキュメント全体を操作する必要がある場合は、RootElement (つまり JsonElement) の Clone を返します。 呼び出し元が JSON ドキュメント内の特定の要素を操作する必要がある場合は、その JsonElementClone を返します。 Clone を作成せずに直接 RootElement またはサブ要素を返した場合、呼び出し元は、返された JsonElement には、それを所有する JsonDocument が破棄されるとアクセスできなくなります。

Clone を作成する必要がある例を次に示します。

public JsonElement LookAndLoad(JsonElement source)
{
    string json = File.ReadAllText(source.GetProperty("fileName").GetString());

    using (JsonDocument doc = JsonDocument.Parse(json))
    {
        return doc.RootElement.Clone();
    }
}

上記のコードでは、fileName プロパティを含む JsonElement が想定されています。 これにより、JSON ファイルが開き、JsonDocument が作成されます。 このメソッドでは、呼び出し元がドキュメント全体を操作することが想定されているため、RootElementClone が返されます。

JsonElement を受け取り、サブ要素を返す場合は、サブ要素の Clone を返す必要はありません。 呼び出し元は、渡された JsonElement が属している JsonDocument が破棄されないように維持する役割を負っています。 次に例を示します。

public JsonElement ReturnFileName(JsonElement source)
{
   return source.GetProperty("fileName");
}

JsonDocument は読み取り専用

System.Text.Json の DOM では、JSON 要素の追加、削除、変更を行うことはできません。 このように設計されている理由は、パフォーマンスを向上させ、一般的な JSON ペイロード サイズ (つまり、< 1 MB) を解析するための割り当てを減らすためです。 現在のシナリオで変更可能な DOM を使用している場合は、次のいずれかの回避策を実行できる可能性があります。

  • JsonDocument を最初から作成する (つまり、既存の JSON ペイロードを Parse メソッドに渡さない場合) には、Utf8JsonWriter を使用して JSON テキストを記述し、そこからの出力を解析して新しい JsonDocument を作成します。
  • 既存の JsonDocument を変更するには、それを使用して JSON テキストを記述し、記述中に変更を行い、そこからの出力を解析して新しい JsonDocument を作成します。
  • 既存の JSON ドキュメントをマージする、つまり、Newtonsoft.JsonJObject.Merge または JContainer.Merge API と同等のことを行うには、この GitHub の問題を参照してください。

JsonElement は union 構造体

JsonDocument では、JsonElement 型 (任意の JSON 要素を含む共用体の構造体型) のプロパティとして RootElement が公開されます。 Newtonsoft.Json では JObjectJArrayJToken などの専用の階層型が使用されます。 JsonElement に対して検索と列挙を行うことができます。また、JsonElement を使用して、JSON 要素を .NET 型に具体化することができます。

JsonDocument と JsonElement でのサブ要素の検索方法

Newtonsoft.JsonJObject または JArray を使用した JSON トークンの検索は、何らかのディクショナリ内での検索であるため、比較的高速になる傾向があります。 それに対し、JsonElement での検索にはプロパティの順次検索が必要になるため、比較的低速になります (たとえば、TryGetProperty を使用する場合)。 System.Text.Json は、検索時間ではなく、初期解析時間を最小限に抑えるように設計されています。 そのため、JsonDocument オブジェクトで検索する場合は、次の方法を使用してパフォーマンスを最適化してください。

  • 独自のインデックス作成やループを実行するのではなく、組み込みの列挙子 (EnumerateArrayEnumerateObject) を使用します。
  • RootElement を使用して、JsonDocument 全体ですべてのプロパティの順次検索を行わないでください。 代わりに、JSON データの既知の構造に基づいて、入れ子になった JSON オブジェクトで検索します。 たとえば、Student オブジェクトで Grade プロパティを探している場合は、Student オブジェクトをループし、それぞれの Grade の値を取得するようにします。すべての JsonElement オブジェクトで Grade プロパティを検索することは避けてください。 後者を行うと、同じデータに対して不要なパスが行われます。

コード例については、「JsonDocument を使用してデータにアクセスする」を参照してください。

JsonTextReader と比較した Utf8JsonReader

System.Text.Json.Utf8JsonReader は、UTF-8 でエンコードされた JSON テキスト用の、ハイ パフォーマンス、低割り当て、順方向専用のリーダーです。ReadOnlySpan<byte> または ReadOnlySequence<byte> から読み取られます。 Utf8JsonReader は低レベルの型であり、カスタム パーサーとデシリアライザーを構築するために使用できます。

以下のセクションでは、Utf8JsonReader を使用する場合の推奨されるプログラミング パターンについて説明します。

Utf8JsonReader は ref 構造体

Utf8JsonReader 型は "ref 構造体" であるため、特定の制限があります。 たとえば、ref 構造体以外のクラスまたは構造体にフィールドとして格納することはできません。 ハイ パフォーマンスを実現するには、この型が ref struct である必要があります。これは、入力の ReadOnlySpan<byte> (これ自体が ref 構造体です) をキャッシュする必要があるためです。 さらに、この型は状態が保持されるため変更可能です。 そのため、これは、値ではなく ref で渡してください。 値で渡すと、構造体のコピーが生成され、呼び出し元が状態の変更を確認できません。 これは Newtonsoft.Json とは異なります。Newtonsoft.Json JsonTextReader がクラスであるためです。 ref 構造体の使用方法の詳細については、「安全で効率的な C# コードを記述する」をご覧ください。

UTF-8 テキストを読み取る

Utf8JsonReader を使用しているときに最大限のパフォーマンスを達成するには、UTF-16 文字列としてではなく、UTF-8 テキストとして既にエンコードされている JSON ペイロードを読み取ります。 コード例については、「Utf8JsonReader を使用してデータをフィルター処理する」を参照してください。

ストリームまたは PipeReader で読み取る

Utf8JsonReader では、UTF-8 でエンコードされた ReadOnlySpan<byte> または ReadOnlySequence<byte> (これは PipeReader からの読み取りの結果です) からの読み取りがサポートされます。

同期読み取りの場合は、ストリームの末尾に到達するまで JSON ペイロードをバイト配列に読み込み、それをリーダーに渡すことができます。 (UTF-16 としてエンコードされた) 文字列からの読み取りの場合は、UTF8.GetBytes を呼び出して、 最初にその文字列を UTF-8 でエンコードされたバイト配列にトランスコードします。 その後、それを Utf8JsonReader に渡します。

Utf8JsonReader では入力が JSON テキストと見なされるため、UTF-8 バイト オーダー マーク (BOM) は無効な JSON と見なされます。 呼び出し元は、それをフィルターで除外してからデータをリーダーに渡す必要があります。

コード例については、「Utf8JsonReader を使用する」を参照してください。

マルチセグメントの ReadOnlySequence で読み取る

JSON の入力が ReadOnlySpan<byte> の場合、各 JSON 要素には、読み取りループを実行するときにリーダーの ValueSpan プロパティからアクセスできます。 ただし、入力が ReadOnlySequence<byte> (これは、PipeReader からの読み取りの結果です) である場合、一部の JSON 要素が ReadOnlySequence<byte> オブジェクトの複数のセグメントにまたがることがあります。 これらの要素には、連続メモリ ブロック内で ValueSpan からアクセスすることはできません。 代わりに、マルチセグメントの ReadOnlySequence<byte> を入力として使用する場合は必ず、リーダーで HasValueSequence プロパティをポーリングして、現在の JSON 要素へのアクセス方法を確認します。 推奨されるパターンを次に示します。

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

プロパティ名の検索に ValueTextEquals を使用する

プロパティ名の検索用に SequenceEqual を呼び出してバイト単位の比較を実行する場合は、ValueSpan を使用しないでください。 代わりに ValueTextEquals を呼び出してください。このメソッドにより、JSON でエスケープされた文字がエスケープ解除されるためです。 以下は、"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 許容値型に null 値を読み込む

Newtonsoft.Json には、Nullable<T> を返す API が用意されています。たとえば、bool? を返すことで Null TokenType を処理する ReadAsBoolean などがあります。 組み込みの System.Text.Json の API では、null 非許容値型のみが返されます。 たとえば、Utf8JsonReader.GetBoolean では bool が返されます。 JSON で Null が見つかると、例外がスローされます。 次の例は、null を処理する 2 つの方法を示しています。1 つは null 許容値型を返す方法で、もう 1 つは既定値を返す方法です。

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

マルチターゲット

特定のターゲット フレームワークに対して Newtonsoft.Json の使用を継続する必要がある場合は、マルチターゲットにし、実装を 2 つにすることができます。 ただし、これは簡単ではなく、いくつかの #ifdefs とソースの複製が必要になります。 できるだけ多くのコードを共有する 1 つの方法として、Utf8JsonReaderNewtonsoft.Json JsonTextReader を囲む ref struct ラッパーを作成する方法があります。 このラッパーでは、動作の違いを隔離しつつ、公開されている表面部分は統合されます。 これにより、変更を主に型の構造に隔離すると共に、新しい型を参照によって渡すことができます。 これは、Microsoft.Extensions.DependencyModel ライブラリが従っているパターンです。

JsonTextWriter と比較した Utf8JsonWriter

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

以下のセクションでは、Utf8JsonWriter を使用する場合の推奨されるプログラミング パターンについて説明します。

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

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

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

生の値を書き込む

Newtonsoft.Json WriteRawValue メソッドでは、値が必要な生の JSON が書き込まれます。 System.Text.Json には、それに直接相当するものはありませんが、有効な JSON のみが書き込まれるようにする回避策を次に示します。

using JsonDocument doc = JsonDocument.Parse(string);
doc.WriteTo(writer);

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

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

JSON 形式をカスタマイズする

JsonTextWriter には次の設定が含まれていますが、Utf8JsonWriter には同等のものがありません。

  • Indentation - インデントする文字数を指定します。 Utf8JsonWriter では常に 2 文字のインデントが行われます。
  • IndentChar - インデントに使用する文字を指定します。 Utf8JsonWriter では常に空白文字が使用されます。
  • QuoteChar - 文字列値を囲むために使用する文字を指定します。 Utf8JsonWriter では常に二重引用符が使用されます。
  • QuoteName - プロパティ名を引用符で囲むかどうかを指定します。 Utf8JsonWriter では常に引用符で囲みます。

Utf8JsonWriter によって生成された JSON をこれらの方法でカスタマイズできる回避策はありません。

null 値を書き込む

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

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

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

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

JsonTextWriter には、TimeSpanUrichar の値のための WriteValue メソッドがあります。 Utf8JsonWriter には同等のメソッドがありません。 代わりに、これらの値を文字列として書式設定し (たとえば ToString() を呼び出します)、WriteStringValue を呼び出します。

マルチターゲット

特定のターゲット フレームワークに対して Newtonsoft.Json の使用を継続する必要がある場合は、マルチターゲットにし、実装を 2 つにすることができます。 ただし、これは簡単ではなく、いくつかの #ifdefs とソースの複製が必要になります。 できるだけ多くのコードを共有する 1 つの方法として、Utf8JsonWriterNewtonsoft JsonTextWriter を囲むラッパーを作成する方法があります。 このラッパーでは、動作の違いを隔離しつつ、公開されている表面部分は統合されます。 これにより、主に型の構造に変更を隔離できます。 Microsoft.Extensions.DependencyModel ライブラリは以下に従っています。

TypeNameHandling.All はサポートされていません。

System.Text.Json から TypeNameHandling.All-equivalent 機能を除外するという決定は意図的なものでした。 独自の型情報を指定することを JSON ペイロードに許可することは、Web アプリケーションにおいて、よくある脆弱性の原因です。 特に、TypeNameHandling.AllNewtonsoft.Json を構成すると、JSON ペイロード自体の中に実行可能アプリケーション全体を埋め込むことがリモート クライアントに許可されます。逆シリアル化の場合、組み込まれたコードが Web アプリケーションによって抽出され、実行されます。 詳細については、「Friday the 13th JSON attacks PowerPoint」 (13 日の金曜日の JSON 攻撃)(PowerPoint 資料から) と「Friday the 13th JSON attacks details」 (13 日の金曜日の JSON 攻撃の詳細) を参照してください。

その他の技術情報