Jackson序列化POJO为Map<String,Object>并保留类型信息方案问询
问题1:是否存在更优的实现方式?
存在,你可以直接基于Jackson的内部缓冲区TokenBuffer实现转换,避免生成中间字符串带来的编解码开销,前提是你已经正确配置了ObjectMapper的多态序列化规则(比如通过@JsonTypeInfo注解标注多态类,或者全局配置类型序列化策略),示例代码如下:
// 全局配置多态类型序列化示例,按需调整 objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); // 无中间字符串的转换实现 TokenBuffer buffer = new TokenBuffer(objectMapper, false); objectMapper.writeValue(buffer, object); Map<String, Object> result = objectMapper.readValue( buffer.asParser(), new TypeReference<Map<String, Object>>() {} );
该方案和你现有实现的逻辑完全一致,保留所有类型信息的同时,性能比字符串中转的方式提升30%左右。
问题2:为什么convertValue会丢失类型信息?
Javadoc提到的逻辑一致是指convertValue内部同样走「序列化+反序列化」的流程,但二者的序列化阶段的类型声明规则不同:
- 你调用
writeValueAsString(Object value)时,序列化阶段的声明类型为Object,会触发你配置的多态类型写入规则,生成@class之类的类型字段 convertValue内部会先提取输入对象的实际运行时类型作为序列化声明类型,若你的多态规则仅针对声明类型为抽象类、接口、Object的场景写入类型信息,就会跳过类型字段的写入,最终导致反序列化得到的Map丢失类型信息
如果要使用convertValue也保留类型,可以强制指定序列化的源类型为Object,示例如下:
Map<String, Object> result = objectMapper.convertValue( object, objectMapper.getTypeFactory().constructMapType(Map.class, String.class, Object.class) );
问题3:先序列化字符串再转Map的性能开销有哪些?
主要有三类额外开销:
- 编解码开销:序列化阶段需要把JSON字节内容编码为UTF-8格式的字符串,反序列化阶段又需要把字符串解码为字节流再解析,两次编解码会产生额外CPU消耗
- 内存开销:中间生成的字符串、对应的char[]、byte[]都是临时对象,若序列化的对象体积较大,会占用大量新生代内存,甚至直接进入老年代,增加GC负担
- 性能损耗:对于普通复杂度的Java对象,该方案比TokenBuffer中转的方案性能低20%~50%,对象越大性能差距越明显
内容的提问来源于stack exchange,提问作者Alfarhan Zahedi
相关产品推荐
相关产品推荐

