Spring Boot(Java 8)中如何区分JSON字段缺失与值为null
这确实是Spring Boot JSON反序列化场景里挺常见的一个痛点——默认情况下,JSON里没传的字段和传了null的字段,最终都会让POJO对应的字段变成null,根本没法区分二者。我在实际项目里试过几个可行的方案,分享给你参考:
方案1:先用JsonNode做前置解析,再转换POJO
这个方案思路很直接:先把请求体解析成Jackson的JsonNode对象,利用JsonNode的has("fieldName")方法判断字段是否存在,之后再把JsonNode转换成目标POJO。这样既能拿到业务数据,又能明确区分字段是“未传入”还是“传入了null”。
举个Controller里的示例代码:
@PostMapping("/user") public ResponseEntity<?> handleUserRequest(@RequestBody String requestBody) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); // 先解析为JsonNode判断字段存在性 JsonNode rootNode = objectMapper.readTree(requestBody); boolean hasName = rootNode.has("name"); boolean hasAge = rootNode.has("age"); // 再转换为业务POJO UserPOJO user = objectMapper.treeToValue(rootNode, UserPOJO.class); // 根据标记执行不同逻辑 if (hasName) { // 字段存在(值可能是null或具体内容),执行对应处理 processExistingName(user.getName()); } else { // 字段完全没传入,执行另一套逻辑 processMissingName(); } return ResponseEntity.ok().build(); }
优点:简单直接,不需要修改现有POJO结构,适合快速解决单个接口的问题;
缺点:如果需要判断的字段很多,代码会显得繁琐,Controller逻辑会有点冗余。
方案2:自定义带存在标记的包装类+反序列化器
如果多个接口、多个POJO都需要区分“未传入”和“传入null”,这个方案会更优雅。核心是创建一个包装类,同时记录字段的值和是否存在的状态,再配合自定义Jackson反序列化器实现逻辑。
首先定义包装类:
public class Presentable<T> { private T value; private boolean isPresent; public Presentable() { this.isPresent = false; } public Presentable(T value) { this.value = value; this.isPresent = true; } // 省略getter、setter方法 }
然后写自定义反序列化器:
public class PresentableDeserializer<T> extends JsonDeserializer<Presentable<T>> { private final Class<T> type; public PresentableDeserializer(Class<T> type) { this.type = type; } @Override public Presentable<T> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); T value = node.isNull() ? null : p.getCodec().treeToValue(node, type); // 只要JSON里有这个字段,不管值是不是null,isPresent都设为true return new Presentable<>(value); } }
接下来在POJO里使用这个包装类并指定反序列化器:
public class UserPOJO { @JsonDeserialize(using = PresentableDeserializer.class) private Presentable<String> name; @JsonDeserialize(using = PresentableDeserializer.class) private Presentable<Integer> age; // 省略getter方法 }
最后在Controller里直接使用POJO判断:
@PostMapping("/user") public ResponseEntity<?> handleUserRequest(@RequestBody UserPOJO user) { if (user.getName().isPresent()) { String nameValue = user.getName().getValue(); // 处理字段存在的情况(值可能为null) } else { // 处理字段未传入的情况 } return ResponseEntity.ok().build(); }
优点:代码结构清晰,业务逻辑和存在性判断分离,适合大规模使用的场景;
缺点:需要额外编写包装类和反序列化器,有一定的开发成本。
方案3:利用@JsonSetter和POJO内部标记
这个方案不需要额外的类,直接在POJO里添加存在性标记字段,配合@JsonSetter注解实现判断。原理是:Jackson反序列化时,只要JSON里存在某个字段(不管值是不是null),就会调用对应的setter方法,我们在setter里把标记设为true;如果字段不存在,setter不会被调用,标记保持默认的false。
示例POJO代码:
public class UserPOJO { private String name; private boolean namePresent; // 标记name字段是否传入 private Integer age; private boolean agePresent; // 标记age字段是否传入 @JsonSetter("name") public void setName(String name) { this.name = name; this.namePresent = true; // 调用setter说明字段存在 } @JsonSetter("age") public void setAge(Integer age) { this.age = age; this.agePresent = true; } // 省略getter方法 }
Controller里直接判断标记即可:
@PostMapping("/user") public ResponseEntity<?> handleUserRequest(@RequestBody UserPOJO user) { if (user.isNamePresent()) { // name字段存在,值可能是null或具体内容 } else { // name字段未传入 } return ResponseEntity.ok().build(); }
优点:实现简单,不需要额外依赖或类,侵入性较低;
缺点:POJO会多一些标记字段,字段多的话会有重复代码。
内容的提问来源于stack exchange,提问作者Ananya Antony

