在序列化已选择多态反序列化的类时,如何避免公开类型鉴别器?
我正在使用System.Text.Json的JsonDerivedType
属性来进行多态反序列化。 所以我需要在属性上指定typeDiscriminator
,否则反序列化不会读取 JSON 上的传入$type
字段。 但是当我序列化这些相同的类时,我不希望System.Text.Json自动添加$type
(公开实现细节等)。
例
[JsonDerivedType(typeof(Derived1), typeDiscriminator: "Derived1")]
[JsonDerivedType(typeof(Derived2), typeDiscriminator: "Derived2")]
public record BaseType(int Id);
public record Derived1(int Id, string Name) : BaseType(Id);
public record Derived2(int Id, bool IsActive) : BaseType(Id);
序列化时:
var values = new List<BaseType>
{
new Derived1(123, "Foo"),
new Derived2(456, true)
};
JsonSerializer.Serialize(values);
实际输出:
[
{ "$type": "Derived1", "Id": 123, "Name": "Foo" },
{ "$type": "Derived2", "Id": 456, "IsActive": true }
]
如何避免$type
被写入?
期望输出:
[
{ "Id": 123, "Name": "Foo" },
{ "Id": 456, "IsActive": true }
]
同样,我知道我可以从属性中排除typeDiscriminator
,但是反序列化将不起作用。
链接
- 反序列化 .NET 7 中添加的类型层次结构
一种解决方案是为序列化和反序列化编写单独的DefaultJsonTypeInfoResolver
重写,并删除[JsonDerivedType]
属性。
与Newtonsoft类似,System.Text.Json也支持通过序列化程序的选项作为属性的替代方法进行多态反序列化。 在 Newtonsoft 中,您将使用不同的JsonSerializerSettings.TypeNameHandling
值来影响是否写入$type
。 System.Text.Json 要求您显式选择每种类型加入此行为,但思路是相同的。
示例类:
// Don't add [JsonDerivedType]
public record BaseType(int Id);
public record Derived1(int Id, string Name) : BaseType(Id);
public record Derived2(int Id, bool IsActive) : BaseType(Id);
使用鉴别器反序列化的解析器:
// Example taken from dotnet/runtime#63747 "Configuring Polymorphism via the Contract model"
public class DeserializeResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);
if (jsonTypeInfo.Type == typeof(BaseType))
{
jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions()
{
DerivedTypes =
{
// Pass in the type discriminator for this resolver.
new JsonDerivedType(typeof(Derived1), typeDiscriminator: "Derived1"),
new JsonDerivedType(typeof(Derived2), typeDiscriminator: "Derived2"),
}
};
}
return jsonTypeInfo;
}
}
无需鉴别器即可序列化的解析器:
public class SerializeResolver : DefaultJsonTypeInfoResolver
{
...
// Exact same as above resolver except:
DerivedTypes =
{
// Don't pass in the typeDiscriminator parameter.
new JsonDerivedType(typeof(Derived1)),
new JsonDerivedType(typeof(Derived2)),
}
...
}
然后,使用不同的JsonSerializerOptions
序列化和反序列化实例:
initial
:
[
{ "$type": "Derived1", "Name": "Foo", "Id": 123 },
{ "$type": "Derived2", "IsActive": true, "Id": 456 }
]
string initial;
var deserializeOptions = new JsonSerializerOptions
{
TypeInfoResolver = new DeserializeResolver(),
};
var serializeOptions = new JsonSerializerOptions
{
TypeInfoResolver = new SerializeResolver(),
};
var values = JsonSerializer.Deserialize<List<BaseType>>(initial, deserializeOptions);
// At this point, the types have been successfully deserialized via their $type.
Assert.NotNull(values);
Assert.IsAssignableFrom<Derived1>(values[0]);
Assert.IsAssignableFrom<Derived2>(values[1]);
string result = JsonSerializer.Serialize(values, serializeOptions);
result
:
[
{ "Name": "Foo", "Id": 123 },
{ "IsActive": true, "Id": 456 }
]
我看到以下选项:
- 不要使用多态列表,例如,有一个
Derived1
列表和另一个Derived2
列表 - 将
$type
更改为您喜欢的内容[JsonPolymorphic(CustomTypeDiscriminatorPropertyName = "Kind")]
- 使用其他名称:
[JsonDerivedType(typeof(Derived1), typeDiscriminator: "A")]
或数字[JsonDerivedType(typeof(Derived1), typeDiscriminator: 1)]
, - 不要使用
JsonDerivedType
并编写自己的反序列化器
来源:#63747:开发人员可以使用 System.Text.Json 安全地序列化类型层次结构