System.Text.Json序列化接口类型实例时自定义转换器不生效问题
定义金额接口IAmount、实现类Amount,并为Newtonsoft.Json、System.Text.Json分别编写自定义序列化转换器,代码如下:
public interface IAmount { decimal Value { get; } } [Newtonsoft.Json.JsonConverter(typeof(NewtonsoftJsonConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(SystemTextJsonConverter))] public class Amount : IAmount { public Amount(decimal value) { Value = value; } public decimal Value { get; } } public class NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter { public override bool CanConvert(Type objectType) => objectType.IsAssignableTo(typeof(IAmount)); public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object? value, Newtonsoft.Json.JsonSerializer serializer) { writer.WriteRawValue(((IAmount?)value)?.Value.ToString()); } } public class SystemTextJsonConverter : System.Text.Json.Serialization.JsonConverter<object> { public override bool CanConvert(Type typeToConvert) => typeToConvert.IsAssignableTo(typeof(IAmount)); public override object Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) { throw new NotImplementedException(); } public override void Write(System.Text.Json.Utf8JsonWriter writer, object value, System.Text.Json.JsonSerializerOptions options) { writer.WriteRawValue(((IAmount)value).Value.ToString()); } }
当序列化对象的编译时类型为具体实现类Amount时,两种序列化器均可正常工作,测试代码及对应输出如下:
var foo = new Amount(10); Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(foo)); // 输出10 Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(foo)); // 输出10
当序列化对象的编译时类型为接口IAmount时,Newtonsoft.Json可正常调用自定义转换器输出10,但System.Text.Json不会触发自定义转换器,直接输出{"Value":10},调试时可发现转换器的CanConvert方法从未被调用。
已知在IAmount接口上添加[System.Text.Json.Serialization.JsonConverter(typeof(SystemTextJsonConverter))]特性可修复该问题,但要求不修改接口定义,且项目不允许切换回Newtonsoft.Json。
问题由两款库的转换器解析逻辑差异导致:
- Newtonsoft.Json解析转换器时,会同时检查编译时声明类型、运行时实际类型上的特性标注,因此即使声明类型是
IAmount,也能识别到运行时Amount类上挂载的转换器 - System.Text.Json默认解析转换器时,若编译时声明类型为接口,不会主动扫描运行时实现类上的转换器特性,因此写在
Amount类上的转换器不会被加载
不需要修改接口定义,直接将自定义转换器显式注册到序列化使用的JsonSerializerOptions的Converters集合中即可,该方式既支持单次序列化临时传入配置,也支持全局统一配置:
// 配置序列化选项,注册自定义转换器 var jsonOptions = new JsonSerializerOptions { Converters = { new SystemTextJsonConverter() } }; IAmount amount = new Amount(10); // 传入配置后序列化,会正常输出10 Console.WriteLine(JsonSerializer.Serialize(amount, jsonOptions));
如果是在ASP.NET Core等项目中使用,直接把转换器加到全局JSON配置的Converters列表里,所有IAmount类型的序列化、反序列化都会自动走自定义逻辑。
额外优化建议:当前的SystemTextJsonConverter继承自JsonConverter<object>,可以改为直接继承JsonConverter<IAmount>,不需要重写CanConvert方法,泛型约束会自动完成类型匹配,执行效率更高。
内容的提问来源于stack exchange,提问作者nfplee

