前言
今天在项目中遇到了一个Gson转换数字后会变为Double类型引起的Bug,特此记录一下。
背景是这样的,我们对于前端请求,有一个公共处理Controller,并根据请求里的接口名称将其分发给其他处理类(Controller)。
因为每个处理类的请求类Req和返回Res是不同的,但是它们的响应code和原因是可以提取的,因此拿到前端数据后,我们后台会处理并返回数据。
Req如下,我们会根据前端在Header和传过来的data数据组成如下Req分发给指定apiName的Controller。
1 |
|
还有一个业务实体类TestReq,如下:
1 |
|
这一切都是很正常的,直到我们升级了FastJson的版本后,便出现了异常。
问题排查
尝试定位问题,发现是枚举值转换抛出的异常,我们有一个枚举值,如下:
1 | public enum Type { |
前端传过来的为1,我们转换传A,这样。
跟踪了一下,发现原来过来的是”1”,但是现在变成了”1.0”。因此转换异常了。
继续检查,发现前端传的数据为int类型的1(其实我们接口定义的String),如下:
1 | {"apiName":"test","version":"v1","data":{"amount":"3672.0","type":1}} |
然后我们系统会将该数据转化为CommonReq
转化的时候我们发现TestReq
里得到的type已经是1.0了。
继续检查发现在前端数据请求过来后,使用的是Gson进行转化的,由于不知道泛型T的具体类型,因此转换逻辑如下:
1 | CommonReq commonReq = GsonUtil.json2Bean(str,CommonReq.class); |
我自己编写了测试类,经过测试,发现Gson确实会把1转换为1.0,但针对的是该类型不明的情况,如下:
1 | public class MyTest { |
问题分析
但我观察之前请求的日志,前端传的也是int类型的1,但转换后就是”1”,而不是”1.0”,这又是什么原因呢。
由于我们升级过一次FastJson版本,但Gson的问题和FastJson又有什么关系呢?
因此继续排查,我发现了一个特别的地方。
1 | if(commonReq.getData() instanceof LinkedHashMap||commonReq.getData() instanceof LinkedTreeMap) { |
其重点在于FastJsonConvert.convertObjectToJSON(reqVo.getData())
这段代码上。
我们看上面的列子,使用Gson转换后CommonReq
里的data对象为LinkedTreeMap
,不能直接强转为TestReq
对象,因此借助了FastJson将其转换为jsonStr,然后再转回来。
这样就会不同吗?
在升级之前,我们使用的FastJson是1.2.10,升级后为1.2.70。
我们使用1.2.10的FastJson,对Gson得到的LinkedTreeMap
进行转换输出,如下:
1 | <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> |
1 | public class MyTest { |
可以看到type为1.
我们继续使用1.2.70版本的FastJson进行试验时,可以看到它输出了1.0.
我们实际对FastJson进行测试:
1 |
|
1 | TestVO testVO = new TestVO(); |
在1.2.10版本下,FastJson输出了{"f":1}
,在1.2.70版本下,输出了{"f":1.0}
。
在低版本下,未指定对象类型情况下,FastJson对于小数数字末尾包含0的,都会舍去。
到这里其实问题也比较清楚了,由于Gson对于未指定类型的数字,会将其转换成Double类型,而FastJson低版本中,对于未指定的浮点数字,如果末尾为0,就会去掉,进而显示整数,而在高版本里解决了这个问题。
我们系统升级了FastJson,因而出现了问题。
但归根结底这个问题是由Gson引起的,因为Gson对于未指定类型的数字,会将其转换成Double类型,至目前原作者也没有修复这个问题。
总结
通过这个问题排查总结,我们能从中学到一点有用的东西。
在一个项目中尽量使用一种Json转换工具,如Jackson、Fastjson、Gson,将它们在项目中混用既不方便维护,也加大了问题的排查难度,而且不同的Json转换工具转换出来的Json串可能相互处理起来并不友好。
这个问题,我们最终将Gson替换为Fastjson,从而解决了问题。
可以看到只使用FastJson,我们得到的数据不会被转换为Double。
在与客户端商定报文格式时,客户端应尽量传送的报文格式与服务端定义的类型一致。
关于如果使用Gson,如何避免出现未指定类型的整数转换为double的问题,可以参考这篇文章。
后记
其实客户端应该上送String类型的1而不是int类型的,但考虑但客户端需要发版,且旧版本客户端后端仍需要兼容,进而改为让服务端进行兼容改造,客户端迭代进行变更处理。