【问题背景】
在基于Spring Boot的RESTful API应用中,前端需要传递一个带有枚举类型字段的JSON请求体。当用户未选择过滤条件时,前端传递了空字符串("")作为枚举参数值,导致后端API调用失败并返回HTTP 400错误。
这个问题可以前端处理,也可以后端处理,推荐后端处理,增加代码稳固性
需要知道的原理:Spring Boot默认使用Jackson作为其JSON处理库
Jackson在将JSON数据转换为Java对象时,遵循一个结构化的处理流程:
- 解析JSON文本:首先将JSON文本解析为令牌流
- 确定目标类型:根据目标Java类型确定如何处理这些令牌
- 查找反序列化器:为每个字段查找合适的反序列化器
- 执行反序列化:使用找到的反序列化器将JSON数据转换为Java对象
对于枚举类型,Jackson默认使用EnumDeserializer,它会尝试将JSON字符串值与枚举常量名进行精确匹配。
【错误信息与原因】
错误信息:
"Cannot deserialize value of type `XxxEnum` from String "": not one of the values accepted for Enum class: [EnumA,EnumB,EnumC]"
原因分析:
当Jackson(Spring Boot默认的JSON处理库)尝试将前端传来的空字符串("")转换为枚举类型时,由于空字符串不匹配任何已定义的枚举常量,Jackson抛出了InvalidFormatException异常。(此时还没有执行方法的代码,而是在处理参数,这个错误发生在HTTP请求处理的早期阶段 - 请求体反序列化过程中,甚至在进入控制器方法之前就已经失败。)
【解决方案】
方法:自定义枚举反序列化器
1. 在DTO类中添加注解:
public class QueryDTO {
@JsonDeserialize(using = XxxEnumDeserializer.class)
private XxxsEnum status;
// 其他字段省略
}
2. 创建自定义反序列化器:
public class XxxEnumDeserializer extends JsonDeserializer<XxxEnum> {
@Override
public XxxEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
if (value == null || value.isEmpty()) {
return null;
}
return XxxEnum.valueOf(value);
}
}
该反序列化器的核心逻辑是:当遇到空字符串时返回null,而不是尝试查找对应的枚举值。这样当查询条件中的status为null时,相应的查询条件不会被添加到SQL查询中,从而实现查询所有状态的数据。
自定义反序列化器会拦截Jackson的默认反序列化过程,采用自己的拦截过程
【总结】
相比修改前端代码,这种后端适应性处理也提高了系统的健壮性和用户体验。