.NET 8下自定义转换器的多态JSON序列化/反序列化异常问题
我有一组使用多态特性的类,通过OData控制器推送给客户端。客户端通过在JsonSerializerOptions中添加自定义转换器来处理对象的序列化/反序列化,以此确定正确的类型。服务端的类已经标记了[JsonDerivedType...]特性,OData会把类型信息存在@odata.type属性里。
我用的泛型TypeDiscriminatingConverter继承自JsonConverter,通过读取@odata.type属性来完成正确类型的序列化/反序列化,但升级到.NET 8后,客户端抛出了如下异常:
System.NotSupportedException: The converter for derived type 'xy' does not support metadata writes or reads. at System.Text.Json.ThrowHelper.ThrowNotSupportedException_BaseConverterDoesNotSupportMetadata(Type derivedType) at System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver..ctor(JsonSerializerOptions options, JsonPolymorphismOptions polymorphismOptions, Type baseType, Boolean converterCanHaveMetadata) at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0() at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured()
我查了资料,还深入分析了System.Text.Json 8.0.1的源码,发现PolymorphicTypeResolver要求使用类型鉴别器时,转换器必须支持元数据,但CanHaveMetadata是内部密封属性,没法在自定义转换器里设置。请问该怎么解决?我漏了什么关键点?
附可复现问题的极简控制台应用代码:
// See https://aka.ms/new-console-template for more information using System.Text.Json; using System.Text.Json.Serialization; string derivedjson = @"{\"$type\":0,\"@odata.type\":\"derived\",\"BaseName\":\"base1\",\"Name\":\"derived1\"}"; JsonSerializerOptions options = new JsonSerializerOptions() { Converters = { new TypeDiscriminatingConverter<Base>((ref Utf8JsonReader reader) => { using var doc = JsonDocument.ParseValue(ref reader); var typeDiscriminator = doc.RootElement.GetProperty("@odata.type").GetString(); if (typeDiscriminator!.Contains("derived")) { return typeof(Derived); } throw new JsonException(); }) }, WriteIndented = false, ReferenceHandler = ReferenceHandler.IgnoreCycles, PropertyNameCaseInsensitive = true, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; var deserialized = JsonSerializer.Deserialize<Base>(derivedjson, options); public class BaseBase { public string BaseBaseName { get; set; } = "basebase"; } public enum PublicEnum { PublicEnumValue = 0 } [JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)] [JsonDerivedType(typeof(Derived), typeDiscriminator: nameof(PublicEnum.PublicEnumValue))] public class Base:BaseBase { public string BaseName { get; set; } = "base"; } public class Derived:Base { public string Name { get; set; } = "derived"; } public class TypeDiscriminatingConverter<T> : JsonConverter<T> { public delegate Type TypeDiscriminatorConverter(ref Utf8JsonReader reader); private readonly TypeDiscriminatorConverter Converter; private Dictionary<Type, string> _typeToPropertyString = new Dictionary<Type, string> { {typeof(Derived),$"\"$type\":\"0\","}, }; public TypeDiscriminatingConverter(TypeDiscriminatorConverter converter) => (Converter) = (converter); public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(T); public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var typeCalculatorReader = reader; var actualType = Converter(ref typeCalculatorReader); return (T?)JsonSerializer.Deserialize(ref reader, actualType, options); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (value is null) { writer.WriteNullValue(); } else { var type = value.GetType(); if (_typeToPropertyString.TryGetValue(type, out string? typeString)) { string temp = JsonSerializer.Serialize(value, type, options); temp = temp.Insert(1, typeString); writer.WriteRawValue(temp); } else { JsonSerializer.Serialize(writer, value, type, options); } } } }
解决方案
移除基类的多态特性:.NET 8中,当你为基类添加
[JsonPolymorphic]和[JsonDerivedType]特性后,System.Text.Json会自动启用内置多态处理逻辑,这和自定义转换器的逻辑冲突,导致抛出元数据支持异常。由于自定义转换器已经完全接管类型鉴别,不需要依赖内置特性,直接删除这些标记即可。避免转换器递归调用:在
Read和Write方法中,反序列化/序列化实际类型时,要创建排除当前自定义转换器的JsonSerializerOptions副本,否则会陷入无限递归。修改后的核心逻辑如下:
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { var typeCalculatorReader = reader; var actualType = Converter(ref typeCalculatorReader); // 创建排除当前转换器的选项副本 var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveAll(c => c.GetType() == typeof(TypeDiscriminatingConverter<T>)); return (T?)JsonSerializer.Deserialize(ref reader, actualType, newOptions); } public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) { if (value is null) { writer.WriteNullValue(); return; } var type = value.GetType(); var newOptions = new JsonSerializerOptions(options); newOptions.Converters.RemoveAll(c => c.GetType() == typeof(TypeDiscriminatingConverter<T>)); if (_typeToPropertyString.TryGetValue(type, out string? typeString)) { string temp = JsonSerializer.Serialize(value, type, newOptions); temp = temp.Insert(1, typeString); writer.WriteRawValue(temp); } else { JsonSerializer.Serialize(writer, value, type, newOptions); } }
- 统一类型鉴别符:确保序列化和反序列化使用一致的类型属性,比如依赖OData的
@odata.type,就把转换器中写入的$type改为@odata.type,避免逻辑混淆:
private Dictionary<Type, string> _typeToPropertyString = new Dictionary<Type, string> { {typeof(Derived), "\"@odata.type\":\"derived\","}, };
内容的提问来源于stack exchange,提问作者phil ius

