Gson转换遇到的问题

前言

今天在项目中遇到了一个Gson转换数字后会变为Double类型引起的Bug,特此记录一下。

背景是这样的,我们对于前端请求,有一个公共处理Controller,并根据请求里的接口名称将其分发给其他处理类(Controller)。

因为每个处理类的请求类Req和返回Res是不同的,但是它们的响应code和原因是可以提取的,因此拿到前端数据后,我们后台会处理并返回数据。

Req如下,我们会根据前端在Header和传过来的data数据组成如下Req分发给指定apiName的Controller。

1
2
3
4
5
6
7
8
9
10
@Data
public class CommonReq<T> {
private String phone;
private String userId;
private String apiName;
private String device;
private String platform;
private String version;
private T data;
}

还有一个业务实体类TestReq,如下:

1
2
3
4
5
@Data
public class TestReq {
private String type;
private BigDecimal amount;
}

这一切都是很正常的,直到我们升级了FastJson的版本后,便出现了异常。

问题排查

尝试定位问题,发现是枚举值转换抛出的异常,我们有一个枚举值,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public enum Type {
A("1", "A"),
B("2", "B");
private String code;
private String desc;
Type(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public static Type getEnum(String code) {
Optional<Type> optional = Arrays.stream(Type.values()).filter(e -> e.code.equals(code)).findFirst();
return optional.orElse(null);
}
}

前端传过来的为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
2
3
4
5
6
7
8
public class MyTest {
public static void main(String[] args) {
String str = "{\"apiName\":\"test\",\"version\":\"v1\",\"data\":{\"amount\":\"3672.0\",\"type\":1}}";
CommonReq commonReq = GsonUtil.json2Bean(str,CommonReq.class);
LinkedTreeMap linkedTreeMap = (LinkedTreeMap) commonReq.getData();
System.out.println(linkedTreeMap.get("type"));;
}
}

upload successful

问题分析

但我观察之前请求的日志,前端传的也是int类型的1,但转换后就是”1”,而不是”1.0”,这又是什么原因呢。

由于我们升级过一次FastJson版本,但Gson的问题和FastJson又有什么关系呢?

因此继续排查,我发现了一个特别的地方。

1
2
3
4
if(commonReq.getData() instanceof LinkedHashMap||commonReq.getData() instanceof LinkedTreeMap) {
String jsonStr = FastJsonConvert.convertObjectToJSON(commonReq.getData());
commonReq.setData(apiService.convertRequest(jsonStr));
}

其重点在于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
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.10</version>
</dependency>
1
2
3
4
5
6
7
8
public class MyTest {
public static void main(String[] args) {
String str = "{\"apiName\":\"test\",\"version\":\"v1\",\"data\":{\"amount\":\"3672.0\",\"type\":1}}";
CommonReq commonReq = GsonUtil.json2Bean(str,CommonReq.class);
String jsonStr = FastJsonUtil.bean2Json(commonReq.getData());
System.out.println(jsonStr);;
}
}

upload successful

可以看到type为1.

我们继续使用1.2.70版本的FastJson进行试验时,可以看到它输出了1.0.

upload successful

我们实际对FastJson进行测试:

1
2
3
4
@Data
public class TestVO {
private Object f;
}
1
2
3
4
TestVO testVO = new TestVO();
testVO.setF(1.0);
String jsonStr = FastJsonUtil.bean2Json(testVO);
System.out.println(jsonStr);

在1.2.10版本下,FastJson输出了{"f":1},在1.2.70版本下,输出了{"f":1.0}

在低版本下,未指定对象类型情况下,FastJson对于小数数字末尾包含0的,都会舍去。

到这里其实问题也比较清楚了,由于Gson对于未指定类型的数字,会将其转换成Double类型,而FastJson低版本中,对于未指定的浮点数字,如果末尾为0,就会去掉,进而显示整数,而在高版本里解决了这个问题。

我们系统升级了FastJson,因而出现了问题。

但归根结底这个问题是由Gson引起的,因为Gson对于未指定类型的数字,会将其转换成Double类型,至目前原作者也没有修复这个问题。

总结

通过这个问题排查总结,我们能从中学到一点有用的东西。

  1. 在一个项目中尽量使用一种Json转换工具,如Jackson、Fastjson、Gson,将它们在项目中混用既不方便维护,也加大了问题的排查难度,而且不同的Json转换工具转换出来的Json串可能相互处理起来并不友好。

  2. 这个问题,我们最终将Gson替换为Fastjson,从而解决了问题。

    upload successful

    可以看到只使用FastJson,我们得到的数据不会被转换为Double。

  3. 在与客户端商定报文格式时,客户端应尽量传送的报文格式与服务端定义的类型一致。

  4. 关于如果使用Gson,如何避免出现未指定类型的整数转换为double的问题,可以参考这篇文章。

    how-can-i-prevent-gson-from-converting-integers-to-doubles

后记

其实客户端应该上送String类型的1而不是int类型的,但考虑但客户端需要发版,且旧版本客户端后端仍需要兼容,进而改为让服务端进行兼容改造,客户端迭代进行变更处理。




-------------文章结束啦 ~\(≧▽≦)/~ 感谢您的阅读-------------

您的支持就是我创作的动力!

欢迎关注我的其它发布渠道