一、背景
工作中可能会遇到对 Map
本文简单探讨下该问题,并给出解决方案,如果你想直接看建议,直接翻到第三部分即可。
二、研究
本文主要以 jackson、 gson、fastjson 三个库为例,版本分别如下:
com.fasterxml.jackson.core jackson-core 2.13.0 com.fasterxml.jackson.core jackson-databind 2.13.0 com.alibaba fastjson 1.2.78 com.google.code.gson gson 2.8.8
代码示例
package json; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.GsonBuilder; import java.util.HashMap; import java.util.Map; public class ObjectDemo { public static void main(String[] args) throws JsonProcessingException { MapdataMap = new HashMap<>(2); dataMap.put("aInteger", 1); dataMap.put("aLong", 2L); String jsonStr = JSON.toJSONString(dataMap); System.out.println(jsonStr); // fastjson System.out.println("--- fastjson -----"); Map fastMap = JSON.parseObject(jsonStr, new com.alibaba.fastjson.TypeReference
运行结果:
{"aInteger":1,"aLong":2}
--- fastjson -----
key:aLong,value=2,valueClass=class java.lang.Integer
key:aInteger,value=1,valueClass=class java.lang.Integer
--- gson -----
key:aInteger,value=1.0,valueClass=class java.lang.Double
key:aLong,value=2.0,valueClass=class java.lang.Double
--- jackson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aLong,value=2,valueClass=class java.lang.Integer
aLong 虽然原始类型为 Long 但是 fastjson 和 jackson 中被反序列化为 Integer 类型,gson 中被映射为 Double 类型。
我们观察序列化后的 json 字符串:
{"aInteger":1,"aLong":2}
会发现其实 JSON 中并没有包含类型信息,而反序列化的类型为 Map.class 或者 Map
因此不同的JSON 序列化工具给出了自己的默认处理行为。
当我们把 aLong 的值调整到 超过 (Integer.MAX_VALUE,Long.MAX_VALUE] 的范围之间时,fastjson 和 jackson 可以解析为 Long 类型。
MapdataMap = new HashMap<>(2); dataMap.put("aInteger", 1); dataMap.put("aLong", Long.MAX_VALUE);
输出的结果:
{"aInteger":1,"aLong":9223372036854775807}
--- fastjson -----
key:aLong,value=9223372036854775807,valueClass=class java.lang.Long
key:aInteger,value=1,valueClass=class java.lang.Integer
--- gson -----
key:aInteger,value=1.0,valueClass=class java.lang.Double
key:aLong,value=9.223372036854776E18,valueClass=class java.lang.Double
--- jackson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aLong,value=9223372036854775807,valueClass=class java.lang.Long
我们大致了解到, fastjson 和 jackson 默认情况下整数类型优先选取 Integer ,超过 Integer 范围再选择 Long ,以此类推。
而当我们放入 Float 类型时,结果又有差异:
MapdataMap = new HashMap<>(2); dataMap.put("aInteger", 1); dataMap.put("aFLoat", 0.1F);
运行结果:
{"aInteger":1,"aFLoat":0.1}
--- fastjson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aFLoat,value=0.1,valueClass=class java.math.BigDecimal
--- gson -----
key:aInteger,value=1.0,valueClass=class java.lang.Double
key:aFLoat,value=0.1,valueClass=class java.lang.Double
--- jackson -----
key:aInteger,value=1,valueClass=class java.lang.Integer
key:aFLoat,value=0.1,valueClass=class java.lang.Double
fastjson 中 Float 被解析为 BigDecimal, gson 和 jackson 中被解析为 Double 类型。
具体底层如何处理,大家可以对每个框架的反序列方法单步跟进去即可得到答案。
这里以 fastjson 为例,简单调试下:
fastjson 底通过 com.alibaba.fastjson.parser.ParserConfig#getDeserializer 方法获取当前类型的反序列化器为 MapDeserializer
执行其反序列化方法:
com.alibaba.fastjson.parser.deserializer.MapDeserializer#deserialze
通过 com.alibaba.fastjson.parser.deserializer.MapDeserializer#parseMap 对 Map 类型进行解析。
由于 Map
跟进 lexer.decimalValue 看下:
最终通过 com.alibaba.fastjson.parser.JSONScanner#decimalValue 将 aFloat 解析为 BigDecimal 类型。
三、如何解决
3.0 将类型写入 JSON 字符串中
如果我们能将原始类型写入到 JSON 字符串中,那么反序列化时自然就可以复原原始的类型。
在 fastjson 中可以使用 SerializerFeature.WriteClassName
package json; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import java.util.HashMap; import java.util.Map; public class JsonDemo { public static void main(String[] args) { MapdataMap = new HashMap<>(2); dataMap.put("aInteger", 1); dataMap.put("aLong", 2L); dataMap.put("aFloat", 3F); String jsonStr = JSON.toJSONString(dataMap, SerializerFeature.WriteClassName); System.out.println(jsonStr); // fastjson System.out.println("--- fastjson -----"); Map fastMap = JSON.parseObject(jsonStr, new com.alibaba.fastjson.TypeReference >() { }); printMap(fastMap); } private static void printMap(Map map) { map.forEach((key, value) -> { System.out.println("key:" + key + ",value=" + value + ",valueClass=" + value.getClass()); }); } }
打印的结果
{"@type":"java.util.HashMap","aFloat":3.0F,"aInteger":1,"aLong":2L}
--- fastjson -----
key:aLong,value=2,valueClass=class java.lang.Long
key:aFloat,value=3.0,valueClass=class java.lang.Float
key:aInteger,value=1,valueClass=class java.lang.Integer
虽然,这种方法可以解决问题,但是这也通常要求序列化和反序列化使用同一个 JSON 工具。
比如上面的 {"@type":"java.util.HashMap","aFloat":3.0F,"aInteger":1,"aLong":2L} 直接使用 jackson 进行反序列化会报错:
System.out.println("--- jackson -----"); ObjectMapper objectMapper = new ObjectMapper(); MapjacksonMap = objectMapper.readValue(jsonStr, new TypeReference >() { }); printMap(jacksonMap);
报错内容:
--- jackson -----
Exception in thread "main" com.fasterxml.jackson.core.JsonParseException: Unexpected character ('F' (code 70)): was expecting comma to separate Object entries
at [Source: (String)"{"@type":"java.util.HashMap","aFloat":3.0F,"aInteger":1,"aLong":2L}"; line: 1, column: 43]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:735)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:659)
3.1 提供 POJO 类,慎对 Map 序列化
强烈建议不要怕麻烦,直接定义 POJO 类。
不仅不受 JSON 框架的约束,而且对方解析时也非常明确,不容易出错。
如工作中在发送MQ 消息时很多人图方便,不想定义POJO 对象,因为这样通常需要打包比较麻烦,就将要传输给其他系统的数据定义为 Map 类型,下游再根据 key 去解析,这是一个非常不好的习惯。
很容易造成上下游类型不一致,造成更换 JSON 反序列化工具时出现故障。
因此发送 MQ 消息时,最好给出相应的 POJO 类。
实际工作中,还遇到有同学将 Map
3.2 反序列化自定义类
如果上游序列化是 Map
@lombok.Data public class Data { private Float aFloat; private Integer aInteger; }
MapdataMap = new HashMap<>(2); dataMap.put("aInteger", 1); dataMap.put("aFLoat", 0.1F); String jsonStr = JSON.toJSONString(dataMap); Data data = JSON.parseObject(jsonStr, Data.class); System.out.println(data);
输出结果:
Data(aFloat=0.1, aInteger=1)
可能有些同学会觉得定义 POJO 类很麻烦,其实我们可以使用 IDEA 插件或者在线工具实现 JSON 字符串生成 POJO 类。
如 Json2Pojo IDEA 插件
和一些在线生成工具:
https://json2csharp.com/json-to-pojo
https://www.javainuse.com/pojo
3.3 其他
可能网上还会有其他解决方案,比如自定义序列化和反序列化器。
我个人不太建议这么做,因为这样不够通用,跨系统使用不太方便。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。