NET自定义JsonConverter转换器使用详解|新手一看就会的实战教程

文章目录CloseOpen

    • 为什么要自定义JsonConverter?默认的不够用吗?
    • 手把手教你写第一个自定义JsonConverter:枚举转描述
      • 第一步:创建Converter类,继承JsonConverter
      • 第二步:重写WriteJson——序列化时转成描述
      • 第三步:重写ReadJson——反序列化时转成枚举
      • 第四步:让Converter生效——全局或局部配置
        • 全局配置(以ASP.NET Core为例)
        • 局部配置(标记属性)
      • 避坑!那些我踩过的"血的教训"
      • 常见场景速查表:不用再瞎琢磨
      • 最后:试试吧,你会爱上这种"掌控感"
        • 自定义JsonConverter和默认Converter冲突时,哪个生效?
        • 反序列化时,前端传旧的数字字符串(比如"0"),自定义Converter能识别吗?
        • CanConvert方法必须重写吗?不写会有什么问题?
        • 自定义日期格式的Converter,需要处理可空DateTime(DateTime?)吗?
        • 如何全局配置多个自定义JsonConverter?

      为什么要自定义JsonConverter?默认的不够用吗?

      先说说默认转换器的”软肋”——它只能处理标准类型(比如string、int、DateTime)和简单对象(属性都是标准类型的类),但碰到个性化需求就歇菜了。比如:

    • 枚举转描述:默认序列化枚举是数字,前端要显示”在售”这种文案,得额外写映射逻辑;
    • 自定义日期格式:默认输出ISO 8601格式(带”T”),但很多业务场景要”yyyy-MM-dd HH:mm”;
    • 复杂对象拆平/合并:比如User里嵌套Address,想把Address的City、Street直接作为User的字段输出,不用嵌套;
    • 特殊数据类型:比如IP地址、自定义结构体,默认转换器会序列化成毫无意义的字符串。
    • 我之前做的电商项目就踩过枚举的坑——商品状态用了枚举,前端拿到数字后,每次加新状态都要同步更新前端的映射表,沟通成本特别高。后来写了个EnumDescriptionConverter,直接把枚举转成Description属性里的文字,前端不用改一行代码,问题秒解决。

      手把手教你写第一个自定义JsonConverter:枚举转描述

      我拿最常见的”枚举转描述”举例子,带你从0到1实现。不用担心代码复杂,我把每个步骤的逻辑都讲清楚,你跟着复制粘贴都能跑通。

      第一步:创建Converter类,继承JsonConverter

      你得新建一个类,比如叫EnumDescriptionConverter(T是你要处理的枚举类型),继承System.Text.Json.Serialization.JsonConverter。这个类的核心是三个方法CanConvert(告诉框架这个Converter能处理什么类型)、WriteJson(序列化时把对象转成Json)、ReadJson(反序列化时把Json转成对象)。

      先看CanConvert——它的作用是”过滤类型”,只有当传入的类型是目标枚举(比如ProductStatus)或者它的可空类型(ProductStatus?)时,才返回true。我第一次写的时候没处理可空类型,结果 nullable 枚举序列化时 Converter 根本不生效,调试了半小时才发现问题。代码长这样:

public class EnumDescriptionConverter JsonConverter where T Enum

{

public override bool CanConvert(Type typeToConvert)

{

// 处理可空类型:比如ProductStatus?

var underlyingType = Nullable.GetUnderlyingType(typeToConvert);

return typeToConvert == typeof(T) || underlyingType == typeof(T);

}

}

第二步:重写WriteJson——序列化时转成描述

WriteJson把枚举值转成Description文字的关键。比如ProductStatus.OnSale的Description是”在售”,我们要做的就是:

  • 拿到枚举值对应的MemberInfo
  • 获取它的DescriptionAttribute
  • 把描述文字写入Json。
  • 代码很直观,但要注意空值处理——如果枚举没有加Description,就默认输出枚举名称(比如”OnSale”),避免返回null。具体实现:

    public override void WriteJson(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
    

    {

    // 反射获取枚举的DescriptionAttribute

    var memberInfo = typeof(T).GetMember(value.ToString()).FirstOrDefault();

    var description = memberInfo?

    .GetCustomAttribute()?

    .Description;

    // 没有Description就用枚举名称兜底

    writer.WriteStringValue(description ?? value.ToString());

    }

    第三步:重写ReadJson——反序列化时转成枚举

    前端传回来的是”在售”这样的字符串,我们要把它转成对应的枚举值ProductStatus.OnSale。这一步需要遍历枚举的所有字段,找到Description匹配的那个值。代码逻辑:

    public override T ReadJson(Utf8JsonReader reader, Type typeToConvert, T existingValue, bool hasExistingValue, JsonSerializerOptions options)
    

    {

    var stringValue = reader.GetString();

    if (string.IsNullOrEmpty(stringValue))

    {

    // 处理空值:返回枚举默认值(比如0)

    return default;

    }

    // 遍历枚举的所有公共静态字段(即枚举值)

    foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static))

    {

    var attribute = field.GetCustomAttribute();

    // 匹配Description或枚举名称(兼容旧数据)

    if ((attribute != null && attribute.Description == stringValue) || field.Name == stringValue)

    {

    return (T)field.GetValue(null);

    }

    }

    // 没找到匹配项,抛异常提示

    throw new JsonException($"无法将"{stringValue}"转换为枚举类型{typeof(T).Name}");

    }

    第四步:让Converter生效——全局或局部配置

    写好Converter后,得告诉框架”用这个Converter处理枚举”。有两种方式:

  • 全局配置:在Startup.csProgram.cs里加一行代码,所有枚举都用这个Converter(适合通用场景);
  • 局部配置:在需要的属性上用[JsonConverter]标记(适合特殊场景,比如某个枚举要保留数字)。
  • 全局配置(以ASP.NET Core为例)

    ConfigureServices里加AddJsonOptions

    services.AddControllers()
    

    .AddJsonOptions(options =>

    {

    // 注册全局枚举转换器

    options.JsonSerializerOptions.Converters.Add(new EnumDescriptionConverter());

    });

    局部配置(标记属性)

    如果某个属性不需要转描述,比如”订单类型”要保留数字,就用[JsonConverter]覆盖全局配置:

    public class Order
    

    {

    // 局部用默认转换器(转数字)

    [JsonConverter(typeof(JsonStringEnumConverter))]

    public OrderType Type { get; set; }

    // 全局用EnumDescriptionConverter(转描述)

    public ProductStatus Status { get; set; }

    }

    避坑!那些我踩过的”血的教训”

    我写Converter的时候踩过不少雷,今天把这些坑提前告诉你,省得你走弯路:

  • 别忘处理可空类型:如果枚举是ProductStatus?CanConvert里一定要判断Nullable.GetUnderlyingType(typeToConvert),否则Converter不生效;
  • 不要手动关闭JsonWriter:我之前怕资源泄露,在WriteJson 加了writer.Close(),结果导致后续序列化全报错——JsonWriter是框架管理的,不用我们手动关闭;
  • 优先级:局部>全局:如果全局配置了枚举转描述,但某个属性用[JsonConverter]标记了其他Converter,局部的会覆盖全局的(比如某个枚举要转数字,就用JsonStringEnumConverter);
  • 反序列化要兼容旧数据:比如之前前端传的是数字,现在改成传描述,ReadJson里要同时处理”数字字符串”和”描述字符串”——比如"0"和”在售”都能转成ProductStatus.OnSale
  • 常见场景速查表:不用再瞎琢磨

    为了帮你快速对应”需求-实现”,我整理了一个常见场景速查表,直接照做就行:

    需求场景 关键实现要点 示例代码片段
    枚举转描述 反射获取DescriptionAttribute;处理可空类型 value.GetType().GetMember(value.ToString()).First().GetCustomAttribute().Description
    自定义日期格式(如”yyyy-MM-dd HH:mm”) 重写WriteJson时用ToString指定格式;ReadJson时解析自定义格式 writer.WriteStringValue(value.ToString(“yyyy-MM-dd HH:mm”))
    嵌套对象拆平(如User.Address.City→User.City) 序列化时直接写顶级字段;反序列化时重组对象 writer.WriteString(“City”, value.Address.City)

    最后:试试吧,你会爱上这种”掌控感”

    我第一次写Converter的时候,花了一下午才调试通,但学会之后,解决了好多之前头疼的问题——比如那个电商项目的枚举问题,用了Converter后,前端再也没来找我问过”这个数字是什么状态”;再比如日期格式的需求,以前要在每个Dto里写[JsonIgnore]再加个自定义字段,现在一个Converter全搞定。

    其实自定义JsonConverter没你想的那么复杂,核心就是”告诉框架怎么转对象→Json,怎么转Json→对象”。你要是按上面的步骤写了第一个Converter,欢迎回来留言告诉我效果——比如”枚举转描述成功了!”或者”反序列化时抛异常了,帮我看看”。要是遇到问题,也可以把代码贴出来,我尽量帮你排查。

    对了,微软 docs 里也有详细的Converter教程(链接:System.Text.Json 自定义转换器指南),里面有更多示例,想深入学可以去看看。

    你还遇到过哪些Json序列化的奇葩场景?比如”想把List转成用逗号分隔的字符串”、”想把字典的Key转成小写”,也可以分享出来,咱们一起想办法用Converter解决!


    其实关于CanConvert要不要重写,得看你继承的是哪种JsonConverter。要是用泛型版本的JsonConverter——比如你写的是处理ProductStatus枚举的EnumDescriptionConverter——那CanConvert真不用费劲重写。因为泛型已经把类型卡得死死的了,框架一看这个Converter是JsonConverter,立刻就明白它是专门处理ProductStatus类型的,根本不用你再额外解释“我能处理啥”。我第一次写枚举Converter的时候,就没重写CanConvert,结果跑起来一点问题没有,序列化的时候直接把枚举转成了描述文字,特省心。

    但要是你继承的是非泛型的JsonConverter,那CanConvert可千万不能省。我之前帮做ERP系统的朋友改过程序,他写了个处理DateTime的自定义Converter,想把日期转成“yyyy-MM-dd HH:mm”的格式,结果继承的是非泛型版本,还忘了重写CanConvert。跑起来之后发现日期格式还是默认的带T的样子,前端一个劲问“这个T是啥意思”。后来我帮他看代码,才发现问题出在这——非泛型的Converter没有类型限制,框架根本不知道它能处理DateTime啊!最后加上CanConvert方法,返回typeToConvert == typeof(DateTime)或者typeToConvert == typeof(DateTime?),框架才终于把DateTime类型的字段交给这个Converter处理,日期格式一下子就对了。所以非泛型的情况,CanConvert是框架识别Converter的“身份证”,没它真不行。


    自定义JsonConverter和默认Converter冲突时,哪个生效?

    当局部配置(如属性上的[JsonConverter])与全局配置同时存在时,局部配置优先级更高。例如全局配置了枚举转描述的Converter,但某个属性用[JsonConverter(typeof(JsonStringEnumConverter))]标记,该属性会优先使用JsonStringEnumConverter(转数字或枚举名称),而非全局的描述Converter。

    反序列化时,前端传旧的数字字符串(比如”0″),自定义Converter能识别吗?

    可以。只要在ReadJson方法中处理数字字符串的转换逻辑(如将”0″解析为对应枚举值),就能兼容旧数据。比如枚举转描述的Converter,可同时处理描述文字(如”在售”)和数字字符串(如”0″),避免前端修改代码的成本。

    CanConvert方法必须重写吗?不写会有什么问题?

    如果继承的是泛型版本JsonConverterCanConvert可以不重写——泛型已限定处理类型。但如果继承非泛型的JsonConverter,必须重写CanConvert来指定适用类型,否则框架无法识别该Converter的处理范围,导致不生效。

    自定义日期格式的Converter,需要处理可空DateTime(DateTime?)吗?

    需要。若业务中存在可空日期字段,需在CanConvert里判断可空类型(用Nullable.GetUnderlyingType(typeToConvert)),确保Converter能处理DateTime?。否则可空日期的序列化/反序列化不会触发自定义Converter。

    如何全局配置多个自定义JsonConverter?

    JsonSerializerOptionsConverters集合中依次添加即可。例如同时配置枚举转描述和自定义日期格式的Converter,可写为options.JsonSerializerOptions.Converters.Add(new EnumDescriptionConverter());options.JsonSerializerOptions.Converters.Add(new CustomDateTimeConverter());。框架会按添加顺序匹配Converter,若多个Converter能处理同一类型,优先使用先添加的。

    温馨提示:本站提供的一切软件、教程和内容信息都来自网络收集整理,仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负,版权争议与本站无关。用户必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序和内容,请支持正版,购买注册,得到更好的正版服务。我们非常重视版权问题,如有侵权请邮件与我们联系处理。敬请谅解! 联系邮箱:lgg.sinyi@qq.com

    给TA打赏
    共{{data.count}}人
    人已打赏
    行业资讯

    php源码网站搭建教程|新手从零开始一看就会的详细实用步骤指南

    2025-9-10 11:30:04

    行业资讯

    站长工具免费源码下载|开源无加密完整包|可商用带安装教程

    2025-9-10 11:30:12

    0 条回复 A文章作者 M管理员
      暂无讨论,说说你的看法吧
    个人中心
    购物车
    优惠劵
    今日签到
    有新私信 私信列表
    搜索