如何使用Jackson将Enum序列化为String以触发自定义校验返回自定义消息
问题根因
Spring MVC 处理 @RequestBody 请求的流程是先由 Jackson 反序列化 JSON 字符串为 Java 对象,反序列化失败时会直接抛出 HttpMessageNotReadableException,被异常处理器转为 400 Bad Request,此时还未执行 Bean Validation 的校验逻辑,因此你的自定义校验器完全没有触发机会。
可行解决方案
该方案完全保留枚举类型定义,不会影响 Swagger 生成枚举下拉列表,仅调整反序列化逻辑让非法值可以走到校验阶段:
步骤1:实现自定义枚举反序列化器
作用是遇到非法枚举值时不抛出异常,返回 null 让反序列化流程正常走完,进入后续校验阶段,同时可存储非法值用于优化报错信息:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; public class TypeEnumDeserializer extends StdDeserializer<Type> { // 存储用户输入的非法枚举值,用于校验时生成报错信息 public static final ThreadLocal<String> INVALID_ENUM_VALUE = new ThreadLocal<>(); public TypeEnumDeserializer() { super(Type.class); } @Override public Type deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { String value = jsonParser.getText().trim(); for (Type type : Type.values()) { if (type.name().equals(value)) { INVALID_ENUM_VALUE.remove(); return type; } } // 非法值存入ThreadLocal,返回null进入校验逻辑 INVALID_ENUM_VALUE.set(value); return null; } }
步骤2:给枚举绑定自定义反序列化器
直接在你的 Type 枚举上加注解指定反序列化器,枚举类型本身未发生变化,Swagger 仍可正常识别枚举值:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @JsonDeserialize(using = TypeEnumDeserializer.class) public enum Type { MISSING_SITE, INACTIVE_SITE; }
步骤3:调整自定义校验器逻辑
原来的校验逻辑存在冗余:能被反序列化为 Type 类型的参数必然是合法枚举值,无需二次遍历判断。现在仅需判断反序列化结果为 null 的场景即可:
public class ReportTypeValidator implements ConstraintValidator<ValidReportType, Type> { private String globalMessage; @Override public void initialize(ValidReportType constraintAnnotation) { globalMessage = constraintAnnotation.message(); } @Override public boolean isValid(Type type, ConstraintValidatorContext constraintValidatorContext) { if (type == null) { String invalidValue = TypeEnumDeserializer.INVALID_ENUM_VALUE.get(); StringBuilder errorMsg = new StringBuilder(globalMessage + ", report type does not exist"); if (invalidValue != null) { errorMsg.append(", invalid value: ").append(invalidValue); } constraintValidatorContext .buildConstraintViolationWithTemplate(errorMsg.toString()) .addConstraintViolation(); TypeEnumDeserializer.INVALID_ENUM_VALUE.remove(); return false; } TypeEnumDeserializer.INVALID_ENUM_VALUE.remove(); return true; } }
步骤4:调整校验注解作用范围(按需调整)
如果你的 Type 类型是定义在 CustomerParameter 请求实体的字段上,需要给 @ValidReportType 注解的 @Target 增加字段类型支持:
@Constraint(validatedBy = ReportTypeValidator.class) @Target( { ElementType.PARAMETER, ElementType.FIELD }) // 新增FIELD作用范围 @Retention(RetentionPolicy.RUNTIME) @Valid public @interface ValidReportType { String message() default "Invalid value for report type"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
然后在 CustomerParameter 类的 Type 类型字段上加上 @ValidReportType 注解即可生效。
内容的提问来源于stack exchange,提问作者mr.Penguin
相关产品推荐
相关产品推荐

