You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 10:51:22