如何解决JSON反序列化Long变Integer或Double的问题(integer,json,long,开发技术)

时间:2024-05-08 03:02:18 作者 : 石家庄SEO 分类 : 开发技术
  • TAG :

一、背景

工作中可能会遇到对 Map<String,Object> 进行 JSON 序列化,其中值中包含 Long 类型的数据,反序列化后强转 Long 时报类型转换异常的问题。

本文简单探讨下该问题,并给出解决方案,如果你想直接看建议,直接翻到第三部分即可。

二、研究

本文主要以 jackson、 gson、fastjson 三个库为例,版本分别如下:

<!--https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.13.0</version></dependency><!--https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.0</version></dependency><!--https://mvnrepository.com/artifact/com.alibaba/fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.78</version></dependency><!--https://mvnrepository.com/artifact/com.google.code.gson/gson--><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.8</version></dependency>

代码示例

packagejson;importcom.alibaba.fastjson.JSON;importcom.fasterxml.jackson.core.JsonProcessingException;importcom.fasterxml.jackson.core.type.TypeReference;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.google.gson.GsonBuilder;importjava.util.HashMap;importjava.util.Map;publicclassObjectDemo{publicstaticvoidmain(String[]args)throwsJsonProcessingException{Map<String,Object>dataMap=newHashMap<>(2);dataMap.put("aInteger",1);dataMap.put("aLong",2L);StringjsonStr=JSON.toJSONString(dataMap);System.out.println(jsonStr);//fastjsonSystem.out.println("---fastjson-----");Map<String,Object>fastMap=JSON.parseObject(jsonStr,newcom.alibaba.fastjson.TypeReference<Map<String,Object>>(){});printMap(fastMap);System.out.println("---gson-----");Map<String,Object>gsonMap=newGsonBuilder().create().fromJson(jsonStr,(newTypeReference<Map<String,Object>>(){}).getType());printMap(gsonMap);System.out.println("---jackson-----");ObjectMapperobjectMapper=newObjectMapper();Map<String,Object>jacksonMap=objectMapper.readValue(jsonStr,newTypeReference<Map<String,Object>>(){});printMap(jacksonMap);}privatestaticvoidprintMap(Map<String,Object>map){map.forEach((key,value)->{System.out.println("key:"+key+",value="+value+",valueClass="+value.getClass());});}}

运行结果:

{"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<String,Object> 类型,当你只知道这些信息时,你无法得知 aLong 原始类型为 Long 。

因此不同的JSON 序列化工具给出了自己的默认处理行为。

当我们把 aLong 的值调整到 超过 (Integer.MAX_VALUE,Long.MAX_VALUE] 的范围之间时,fastjson 和 jackson 可以解析为 Long 类型。

Map<String,Object>dataMap=newHashMap<>(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 类型时,结果又有差异:

Map<String,Object>dataMap=newHashMap<>(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

如何解决JSON反序列化Long变Integer或Double的问题

通过 com.alibaba.fastjson.parser.deserializer.MapDeserializer#parseMap 对 Map 类型进行解析。

如何解决JSON反序列化Long变Integer或Double的问题

由于 Map<String, Object>的 valueType 类型为 Object,因此对 aFloat 使用 JavaObjectDeserializer 反序列化器进行解析。

如何解决JSON反序列化Long变Integer或Double的问题

跟进 lexer.decimalValue 看下:

如何解决JSON反序列化Long变Integer或Double的问题

最终通过 com.alibaba.fastjson.parser.JSONScanner#decimalValue 将 aFloat 解析为 BigDecimal 类型。

三、如何解决

3.0 将类型写入 JSON 字符串中

如果我们能将原始类型写入到 JSON 字符串中,那么反序列化时自然就可以复原原始的类型。

在 fastjson 中可以使用 SerializerFeature.WriteClassName

packagejson;importcom.alibaba.fastjson.JSON;importcom.alibaba.fastjson.serializer.SerializerFeature;importjava.util.HashMap;importjava.util.Map;publicclassJsonDemo{publicstaticvoidmain(String[]args){Map<String,Object>dataMap=newHashMap<>(2);dataMap.put("aInteger",1);dataMap.put("aLong",2L);dataMap.put("aFloat",3F);StringjsonStr=JSON.toJSONString(dataMap,SerializerFeature.WriteClassName);System.out.println(jsonStr);//fastjsonSystem.out.println("---fastjson-----");Map<String,Object>fastMap=JSON.parseObject(jsonStr,newcom.alibaba.fastjson.TypeReference<Map<String,Object>>(){});printMap(fastMap);}privatestaticvoidprintMap(Map<String,Object>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-----");ObjectMapperobjectMapper=newObjectMapper();Map<String,Object>jacksonMap=objectMapper.readValue(jsonStr,newTypeReference<Map<String,Object>>(){});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<String,Object> 序列化

强烈建议不要怕麻烦,直接定义 POJO 类。

不仅不受 JSON 框架的约束,而且对方解析时也非常明确,不容易出错。

如工作中在发送MQ 消息时很多人图方便,不想定义POJO 对象,因为这样通常需要打包比较麻烦,就将要传输给其他系统的数据定义为 Map 类型,下游再根据 key 去解析,这是一个非常不好的习惯。

很容易造成上下游类型不一致,造成更换 JSON 反序列化工具时出现故障。

因此发送 MQ 消息时,最好给出相应的 POJO 类。

实际工作中,还遇到有同学将 Map<String,Object> 使用 JSON 序列化的方式存储到 Redis 中,然后反序列化后,将原本 Long 类型的值,强转为 Long 导致线上出现BUG(前面讲到,这种情况下使用 fastjson 时,如果值小于整数最大值,反序列化为 Integer 类型,强转必然会报错)。

3.2 反序列化自定义类

如果上游序列化是 Map<String,Object>, 如果类型核实清楚,我们依然可以自定义 POJO 类来反序列化。

@lombok.DatapublicclassData{privateFloataFloat;privateIntegeraInteger;}
Map<String,Object>dataMap=newHashMap<>(2);dataMap.put("aInteger",1);dataMap.put("aFLoat",0.1F);StringjsonStr=JSON.toJSONString(dataMap);Datadata=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

如何解决JSON反序列化Long变Integer或Double的问题

https://www.javainuse.com/pojo

如何解决JSON反序列化Long变Integer或Double的问题

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:如何解决JSON反序列化Long变Integer或Double的问题的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:怎么用span css设置div内部分字体颜色下一篇:

7 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18