填充已初始化的属性
从 .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
)属性的值是一个包含三个元素(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 的新值。 但与引用属性不同,由于对象本身是值类型,因此不会重复使用。 相反,会修改该结构的副本,然后将其重新分配给该属性。 有关示例,请参阅结构属性示例。
结构属性必须具有资源库,否则,将在运行时引发 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];
}
如果执行以下反序列化代码,则 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
值为 5(来自 JSON)。
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 };