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
相关产品推荐
相关产品推荐

