從 .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];
}
如果你執行以下的反序列化程式碼,則 Numbers1 和 Numbers2 都包含 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 };