次の方法で共有


初期化されたプロパティを設定する

.NET 8 以降では、JSON の逆シリアル化時に .NET プロパティを置き換える上書きするかを指定できます。 JsonObjectCreationHandling列挙型は、オブジェクト作成処理の選択肢を提供します。

既定の (置換) 動作

System.Text.Jsonデシリアライザーは、常にターゲット型の新しいインスタンスを作成します。 ただし、新しいインスタンスが作成された場合でも、一部のプロパティとフィールドはオブジェクトの構築の一部として既に初期化されている可能性があります。 次の種類について考えてみましょう。

class A
{
    public List<int> Numbers1 { get; } = [1, 2, 3];
    public List<int> Numbers2 { get; set; } = [1, 2, 3];
}

このクラスのインスタンスを作成すると、 Numbers1 (および Numbers2) プロパティの値は、3 つの要素 (1、2、3) を含むリストになります。 JSON をこの型に逆シリアル化すると、 既定 の動作ではプロパティ値が 置き換えられます

  • Numbers1の場合、値は読み取り専用 (セッターなし) であるため、値 1、2、および 3 は引き続きリストに含まれます。
  • 読み取り/書き込み Numbers2の場合は、新しいリストが割り当てられ、JSON の値が追加されます。

たとえば、次の逆シリアル化コードを実行する場合、 Numbers1 には値 1、2、3 が含まれており、 Numbers2 には値 4、5、6 が含まれます。

A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");

設定の動作

.NET 8 以降では、逆シリアル化の動作を変更して、プロパティとフィールドを置き換える代わりに変更 (設定) できます。

  • コレクション型プロパティの場合、オブジェクトはクリアせずに再利用されます。 コレクションに要素が事前に設定されている場合、それらの要素は JSON からの値と共にデシリアライズされた最終結果に表示されます。 例については、「 Collection プロパティの例」を参照してください。

  • プロパティを持つオブジェクトの場合、変更可能なプロパティは JSON 値に更新されますが、オブジェクト参照自体は変更されません。

  • 構造体型プロパティの場合、有効な動作は、変更可能なプロパティに対して既存の値が保持され、JSON からの新しい値が追加されるということです。 ただし、参照プロパティとは異なり、オブジェクト自体は値型であるため再利用されません。 代わりに、構造体のコピーが変更され、プロパティに再割り当てされます。 例については、「 構造体プロパティの例」を参照してください。

    構造体プロパティにはセッターが必要です。それ以外の場合は、実行時に InvalidOperationException がスローされます。

現在、パラメーター化されたコンストラクターを持つ型には、データ埋め込みの動作が機能しません。 詳細については、 dotnet/runtime の問題 92877 を参照してください。

読み取り専用プロパティ

変更可能な参照プロパティを設定する場合、プロパティ参照のインスタンスが 置き換えられていないため、プロパティにセッターを設定する必要はありません。 この動作は、逆シリアル化によって 読み取り専用 プロパティが設定される可能性があることを意味します。

インスタンスが変更されたコピーに置き換えられるため、構造体プロパティにはセッターが引き続き必要です。

コレクション プロパティの例

Aの例と同じクラス考えてみますが、今回はプロパティを置き換える代わりにプロパティを設定する設定で注釈を付けます。

[JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
class A
{
    public List<int> Numbers1 { get; } = [1, 2, 3];
    public List<int> Numbers2 { get; set; } = [1, 2, 3];
}

次の逆シリアル化コードを実行すると、 Numbers1Numbers2 の両方に値 1、2、3、4、5、6 が含まれます。

A? a = JsonSerializer.Deserialize<A>("""{"Numbers1": [4,5,6], "Numbers2": [4,5,6]}""");

構造体プロパティの例

次のクラスには、逆シリアル化動作が S1 に設定されている構造体プロパティ Populateが含まれています。 このコードを実行すると、 c.S1.Value1 の値は (コンストラクターから) 10 になり、 c.S1.Value2 の値は (JSON から) 5 になります。

C? c = JsonSerializer.Deserialize<C>("""{"S1": {"Value2": 5}}""");

class C
{
    public C()
    {
        _s1 = new S
        {
            Value1 = 10
        };
    }

    private S _s1;

    [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
    public S S1
    {
        get { return _s1; }
        set { _s1 = value; }
    }
}

struct S
{
    public int Value1 { get; set; }
    public int Value2 { get; set; }
}

代わりに既定の Replace 動作が使用された場合、 c.S1.Value1 逆シリアル化後の既定値は 0 になります。 これは、コンストラクター C() が呼び出され、 c.S1.Value1 を 10 に設定した後、S1 の値が新しいインスタンスに置き換えられるためです。 (JSON によって既定値が置き換えられるため、c.S1.Value2 は 5 のままです)。

指定する方法

置換または設定の優先設定を指定するには、複数の方法があります。

  • JsonObjectCreationHandlingAttribute属性を使用して、型またはプロパティ レベルで注釈を付けます。 型レベルで属性を設定し、その Handling プロパティを Populateに設定した場合、動作は、作成が可能なプロパティにのみ適用されます (たとえば、値型にはセッターが必要です)。

    型全体の基本設定を Populateし、その動作から 1 つ以上のプロパティを除外する場合は、型レベルで属性を追加し、プロパティ レベルでもう一度追加して、継承された動作をオーバーライドできます。 そのパターンを次のコードに示します。

    // Type-level preference is Populate.
    [JsonObjectCreationHandling(JsonObjectCreationHandling.Populate)]
    class B
    {
        // For this property only, use Replace behavior.
        [JsonObjectCreationHandling(JsonObjectCreationHandling.Replace)]
        public List<int> Numbers1 { get; } = [1, 2, 3];
        public List<int> Numbers2 { get; set; } = [1, 2, 3];
    }
    
  • グローバル設定を指定するには、 JsonSerializerOptions.PreferredObjectCreationHandling (またはソース生成の場合は JsonSourceGenerationOptionsAttribute.PreferredObjectCreationHandling) を設定します。

    var options = new JsonSerializerOptions
    {
        PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate
    };