共用方式為


填入初始化的屬性

從 .NET 8 開始,你可以設定偏好設定替 填充 .NET 屬性,當 JSON 被反序列化時。 JsonObjectCreationHandling enum 提供了物件建立處理選項:

預設(取代)行為模式

System.Text.Json解序列器總是會建立目標型態的新實例。 然而,即使建立了新實例,某些屬性和欄位可能已經在物件建構過程中被初始化。 請考慮以下類型:

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

當你建立這個類別的實例時, Numbers1 (和 Numbers2)屬性的值是一個包含三個元素(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 的值一起。 舉例請參見 集合性質範例

  • 對於帶有屬性的物件,其可變屬性會更新成 JSON 值,但物件參考本身不會改變。

  • 對於結構型屬性,其有效行為是對其可變屬性保留現有值,並新增 JSON 中的值。 然而,與參考屬性不同的是,物件本身不會被重複使用,因為它是值型別。 取而代之的是修改結構體的副本,然後重新指派到該屬性中。 舉例請參見 Struct 屬性範例

    結構屬性必須有設定器;否則,執行時會拋出 an InvalidOperationException

備註

填充行為目前無法用於有參數化建構子的型別。 欲了解更多資訊,請參閱 dotnet/runtime issue 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 的值會被新實例取代。 (c.S1.Value2 仍然會是 5,因為 JSON 取代了預設值。)

如何指定

有多種方式可以指定偏好,使用替換填充

  • 使用 JsonObjectCreationHandlingAttribute 屬性在類型或屬性層級進行註解。 如果你在類型層級設定屬性,並將其 Handling 屬性設定為 Populate,則行為只適用於那些可以被賦值的屬性(例如,值型別必須具有設定器)。

    如果您希望整個型別的偏好設定為 Populate,但要排除一個或多個屬性,可以在型別層級加入屬性,再在屬性層級重新加入屬性,以覆蓋繼承的行為。 這個模式在以下程式碼中有所展現。

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