从 .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是只读的(没有 setter),所以它的列表中仍然有值 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 中的新值。 但是,与引用属性不同,对象本身不会重复使用,因为它是值类型。 而是修改结构的副本,然后重新分配给该属性。 有关示例,请参阅 结构属性示例。
结构属性必须具有 setter;否则,在运行时会抛出InvalidOperationException。
注释
填充行为当前不适用于具有参数化构造函数的类型。 有关详细信息,请参阅 dotnet/runtime 问题 92877。
只读属性
若要填充可变的引用属性,因为未 替换属性引用的实例,该属性不需要具有 setter。 此行为意味着反序列化还可以填充 只读 属性。
注释
结构体的属性仍然需要 setter,因为实例会被替换为修改过的副本。
集合属性示例
请考虑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,则该行为仅适用于那些可以被赋值的属性(例如,值类型必须具有 setter)。
如果您希望首选项适用于整个类型,但想要从此行为中排除一个或多个属性,您可以在类型级别添加一个属性,并在属性级别再次添加该属性,以覆盖继承的行为。 此模式显示在以下代码中。
// 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 };