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

Spring Boot @RequestParam枚举自定义属性反序列化失效问题

问题根因

两个异常现象来自Spring MVC和Jackson的默认逻辑差异,不存在自定义ObjectMapper失效的问题:

  • 单元测试中直接注入ObjectMapper做JSON反序列化时,@get:JsonValue注解本身是生效的:Jackson枚举反序列化默认逻辑是,一旦枚举存在@JsonValue标记的字段,就仅以该字段值作为唯一匹配依据,不会自动回退匹配枚举原生常量名,因此传registrationNumber可正常解析,传REGISTRATION_NUMBER会失败。
  • Controller层@RequestParam传自定义值报400,是因为@RequestParam的参数解析链路不经过Jackson的ObjectMapper:Spring MVC处理query/form参数时,默认通过内置的字符串转枚举转换器实现类型转换,这套逻辑只会匹配枚举的name()即原生大写常量名,完全不识别Jackson的@JsonValue注解。而接口响应序列化走的是MappingJackson2HttpMessageConverter,会读取@JsonValue配置,因此返回值是自定义的驼峰格式,最终出现序列化、反序列化规则不一致的表现。
解决方案

分两步统一全链路枚举序列化/反序列化规则,同时兼容自定义属性值、枚举原生常量名两种传参:

1. 改造枚举类,增加Jackson反序列化兼容逻辑

给枚举增加@JsonCreator标注的工厂方法,同时匹配@JsonValue自定义值和枚举原生名,解决JSON体传原生枚举名反序列化失败的问题:

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue

enum class PlantProtectionSortColumn(
    @get:JsonValue val propertyName: String,
) {
    NAME("name"),
    REGISTRATION_NUMBER("registrationNumber");

    companion object {
        @JvmStatic
        @JsonCreator
        fun fromValue(value: String): PlantProtectionSortColumn {
            // 优先匹配@JsonValue标记的自定义属性值
            entries.find { it.propertyName == value }?.let { return it }
            // 匹配失败则回退匹配枚举原生常量名
            return valueOf(value)
        }
    }
}

2. 注册全局枚举转换器,对齐@RequestParam参数解析规则

让Spring MVC处理query/form参数时的枚举转换逻辑和Jackson保持一致,解决传自定义值报400的问题。
如果项目只有少量枚举需要适配,可以直接注册对应枚举的转换器:

import org.springframework.core.convert.converter.Converter
import org.springframework.stereotype.Component
import com.fasterxml.jackson.annotation.JsonValue
import java.lang.reflect.Field

@Component
class StringToPlantProtectionSortColumnConverter : Converter<String, PlantProtectionSortColumn> {
    override fun convert(source: String): PlantProtectionSortColumn? {
        // 先匹配@JsonValue对应的自定义值
        PlantProtectionSortColumn.entries.forEach { enumConstant ->
            enumConstant.javaClass.getDeclaredField("propertyName").let {
                it.isAccessible = true
                if (it.get(enumConstant) == source) return enumConstant
            }
        }
        // 匹配失败回退到原生枚举名匹配
        return PlantProtectionSortColumn.valueOf(source)
    }
}

再把转换器注册到Spring MVC配置中:

import org.springframework.context.annotation.Configuration
import org.springframework.format.FormatterRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration
class WebMvcConfig : WebMvcConfigurer {
    override fun addFormatters(registry: FormatterRegistry) {
        registry.addConverter(StringToPlantProtectionSortColumnConverter())
    }
}

如果项目中有多个枚举需要统一这套规则,可以实现通用的ConverterFactory<String, Enum<*>>,一次性注册后所有枚举都会自动走这套匹配逻辑,无需逐个添加转换器。

验证结果

改造完成后所有场景表现一致:

  • JSON请求体传registrationNumber或REGISTRATION_NUMBER都可正常反序列化为对应枚举实例
  • @RequestParam传驼峰自定义值或大写原生枚举名都能正常匹配,不会返回400错误
  • 接口响应返回的枚举值统一为@JsonValue标记的自定义属性值

内容的提问来源于stack exchange,提问作者skorpilvaclav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 15:39:15