ObjectMapper转Map时忽略Null值方法引发结果不一致问题排查
问题:ObjectMapper转换Map时Null值处理逻辑受调用顺序影响
在使用ObjectMapper将对象转换为Map时,遇到了Null值处理逻辑受调用顺序影响的问题。编写的JsonUtil工具类中,toMap方法预期保留Null值,toMapIgnoreNull方法预期忽略Null值,但实际测试发现两者的行为会被调用顺序改变。
工具类代码
@Slf4j public class JsonUtil { private static final ObjectMapper objectMapper; static { objectMapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); objectMapper.registerModule(javaTimeModule); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } public static Map<String, Object> toMap(Object obj) { try { return objectMapper.convertValue(obj, new TypeReference<>() { }); } catch (Exception e) { log.error("failed:", e); return Map.of(); } } public static Map<String, Object> toMapIgnoreNull(Object obj) { try { return objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL) .convertValue(obj, new TypeReference<>() { }); } catch (Exception e) { log.error("failed:", e); return Map.of(); } } }
测试代码
@Data @EqualsAndHashCode(callSuper = false) static class User { private String username; private String password; } public static void johnOut() { User john = new User(); john.setUsername("John"); Map<String, Object> johnMap = toMap(john); System.out.println("result 1: " + johnMap); Map<String, Object> JohnIgnoreNullMap = toMapIgnoreNull(john); System.out.println("result 2: " + JohnIgnoreNullMap); } private static void jimOut() { User jim = new User(); jim.setUsername("jim"); Map<String, Object> jimIgnoreNullMap = toMapIgnoreNull(jim); System.out.println("result 3: " + jimIgnoreNullMap); Map<String, Object> jimMap = toMap(jim); System.out.println("result 4: " + jimMap); } public static void main(String[] args) { johnOut(); jimOut(); }
测试结果
- 当先调用
johnOut()时,输出:
result 1: {username=John, password=null} result 2: {username=John, password=null} result 3: {username=jim, password=null} result 4: {username=jim, password=null}
- 当先调用
jimOut()时,输出:
result 3: {username=jim} result 4: {username=jim} result 1: {username=John} result 2: {username=John}
原因分析
问题出在静态单例的ObjectMapper实例上:
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)是修改全局配置的操作,调用后这个配置会一直生效,后续所有使用该ObjectMapper的操作都会遵循这个规则。- 比如先调用
toMapIgnoreNull,会把全局配置改成忽略Null值,之后再调用toMap时,ObjectMapper已经是忽略Null的状态,自然不会保留Null值;反之先调用toMap,初始配置是保留Null,直到toMapIgnoreNull修改配置后,后续所有方法都用新配置。
解决方法
有几种可行的方案,推荐使用第二种(兼顾性能和线程安全):
方案1:每次调用时创建新的ObjectMapper实例
在每个方法内部创建独立的ObjectMapper,避免全局配置被修改:
public static Map<String, Object> toMap(Object obj) { try { ObjectMapper mapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); mapper.registerModule(javaTimeModule); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); return mapper.convertValue(obj, new TypeReference<>() {}); } catch (Exception e) { log.error("failed:", e); return Map.of(); } } public static Map<String, Object> toMapIgnoreNull(Object obj) { try { ObjectMapper mapper = new ObjectMapper(); JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); mapper.registerModule(javaTimeModule); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper.convertValue(obj, new TypeReference<>() {}); } catch (Exception e) { log.error("failed:", e); return Map.of(); } }
缺点:每次创建ObjectMapper会有一定性能开销,适合调用频率不高的场景。
方案2:基于全局单例创建副本修改配置
利用ObjectMapper的copy()方法创建一个副本,修改副本的配置而不影响原实例:
public static Map<String, Object> toMap(Object obj) { try { // 使用原实例的配置,不做修改 return objectMapper.convertValue(obj, new TypeReference<>() {}); } catch (Exception e) { log.error("failed:", e); return Map.of(); } } public static Map<String, Object> toMapIgnoreNull(Object obj) { try { // 创建副本并修改配置,不影响原单例 ObjectMapper mapper = objectMapper.copy(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return mapper.convertValue(obj, new TypeReference<>() {}); } catch (Exception e) { log.error("failed:", e); return Map.of(); } }
优点:复用原实例的模块和基础配置,性能开销小,同时保证原实例配置不被修改,线程安全。
方案3:使用ObjectWriter处理不同配置
通过ObjectWriter来实现不同的序列化规则,不修改原ObjectMapper的配置:
public static Map<String, Object> toMap(Object obj) { try { return objectMapper.convertValue(obj, new TypeReference<>() {}); } catch (Exception e) { log.error("failed:", e); return Map.of(); } } public static Map<String, Object> toMapIgnoreNull(Object obj) { try { ObjectWriter writer = objectMapper.writer().with(JsonInclude.Include.NON_NULL); // 先序列化为JSON字符串再转回Map,或者直接用writer的convertValue return writer.convertValue(obj, new TypeReference<>() {}); } catch (Exception e) { log.error("failed:", e); return Map.of(); } }
优点:同样不修改原实例配置,ObjectWriter是线程安全的,适合频繁调用的场景。
内容的提问来源于stack exchange,提问作者尚振璞
相关产品推荐
相关产品推荐

