问题描述

前后端分离的开发模式下,前后端交互通常采用JSON格式数据.自然会涉及到json字符串与JAVA对象之间的转换。实现json字符串与Java对象相互转换的工具很多,常用的有Json、Gson、FastJSON、Jackson等。一次测试中,在将返回给前端的json字符串反序列化为自定义的Response对象时,发现原先json中的Integer类型被转化为了Double类型。便于问题描述,对原有json字符串简化,示例如下:


{
"status": 200,
"msg": "OK",
"data": [{
"id": 1,
"username": "eric",
"password": "123456",
"age": 29,
"sex": 0,
"permission": 0,
"isDel": 0
}]
}

使用Gson(版本gson-2.8.5)的fromJson方法解析,


public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
Object object = fromJson(json, (Type) classOfT);
return Primitives.wrap(classOfT).cast(object);
}

解析后,结果如下:


ResponseData(status=200, msg=OK, data=[{id=1.0, username=eric, password=123456, age=29.0, sex=0.0, permission=0.0, isDel=0.0}])

其中ResponseData类定义如下:


@Data
public class ResponseData implements Serializable {
// 响应业务状态
private Integer status; // 响应消息
private String msg; // 响应中的数据
private Object data;
}

发现data字段解析后,原有的Integer类型都转换成了Double类型,而status字段却没有被转换为Double类型。那为什么会出现这种现象呢?

原因分析

跟踪Gson实现json字符串反序列化的源码,在实现具体的Json数据反序列化时,首先会根据传入的对象类型Type获取类型适配器TypeAdapter,然后根据获取的TypeAdapter实现Json值到一个对象的转换。


public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
boolean isEmpty = true;
boolean oldLenient = reader.isLenient();
reader.setLenient(true);
try {
reader.peek();
isEmpty = false;
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);
return object;
} catch (EOFException e) {
// 省略异常处理逻辑
} finally {
reader.setLenient(oldLenient);
}
}

解析中比较关键的就是根据待解析的类型找到对应的类型适配器TypeAdapter<T>类,如果找到类型适配器不合适,就可能造成解析后的数据出问题。类型适配器TypeAdapter是一个抽象类,主要方法如下:


public abstract class TypeAdapter&lt;T&gt; { /**
* Writes one JSON value (an array, object, string, number, boolean or null)
* for {@code value}.
*
* @param value the Java object to write. May be null.
*/
public abstract void write(JsonWriter out, T value) throws IOException; /**
* Reads one JSON value (an array, object, string, number, boolean or null)
* and converts it to a Java object. Returns the converted object.
*
* @return the converted Java object. May be null.
*/
public abstract T read(JsonReader in) throws IOException;
}

解析时,类型适配器TypeAdapter通过read()方法读取Json数据,将其转化为Java对象。那么为什么status字段可以正常转换,而data字段转换确有问题呢?

这是由于在解析status字段时,传入的类型Type是一个Integer类型,在调用getAdapter()方法查找TypeAdapter时,遍历TypeAdapterFactory工厂,能找到一个TypeAdapters.INTEGER_FACTORY工厂,通过这个工厂就可以得到一个适用于解析Integer类型字段的类型适配器。


public &lt;T&gt; TypeAdapter&lt;T&gt; getAdapter(TypeToken&lt;T&gt; type) {
TypeAdapter&lt;?&gt; cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type);
if (cached != null) {
return (TypeAdapter&lt;T&gt;) cached;
} Map&lt;TypeToken&lt;?&gt;, FutureTypeAdapter&lt;?&gt;&gt; threadCalls = calls.get();
boolean requiresThreadLocalCleanup = false;
if (threadCalls == null) {
threadCalls = new HashMap&lt;TypeToken&lt;?&gt;, FutureTypeAdapter&lt;?&gt;&gt;();
calls.set(threadCalls);
requiresThreadLocalCleanup = true;
} // the key and value type parameters always agree
FutureTypeAdapter&lt;T&gt; ongoingCall = (FutureTypeAdapter&lt;T&gt;) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
} try {
FutureTypeAdapter&lt;T&gt; call = new FutureTypeAdapter&lt;T&gt;();
threadCalls.put(type, call); for (TypeAdapterFactory factory : factories) {
TypeAdapter&lt;T&gt; candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
typeTokenCache.put(type, candidate);
return candidate;
}
}
throw new IllegalArgumentException(&quot;GSON (&quot; + GsonBuildConfig.VERSION + &quot;) cannot handle &quot; + type);
} finally {
threadCalls.remove(type); if (requiresThreadLocalCleanup) {
calls.remove();
}
}
}

而data字段对应的类型是Object,则通过getAdapter()方法查找到的是ObjectTypeAdapter类型适配器。所以默认情况下是由ObjectTypeAdapter类完成data字段数据的解析。


/**
* Adapts types whose static type is only 'Object'. Uses getClass() on
* serialization and a primitive/Map/List on deserialization.
*/
public final class ObjectTypeAdapter extends TypeAdapter&lt;Object&gt; {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings(&quot;unchecked&quot;)
@Override public &lt;T&gt; TypeAdapter&lt;T&gt; create(Gson gson, TypeToken&lt;T&gt; type) {
if (type.getRawType() == Object.class) {
return (TypeAdapter&lt;T&gt;) new ObjectTypeAdapter(gson);
}
return null;
}
}; private final Gson gson; ObjectTypeAdapter(Gson gson) {
this.gson = gson;
} @Override public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
List&lt;Object&gt; list = new ArrayList&lt;Object&gt;();
in.beginArray();
while (in.hasNext()) {
list.add(read(in));
}
in.endArray();
return list; case BEGIN_OBJECT:
Map&lt;String, Object&gt; map = new LinkedTreeMap&lt;String, Object&gt;();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map; case STRING:
return in.nextString(); case NUMBER:
return in.nextDouble(); case BOOLEAN:
return in.nextBoolean(); case NULL:
in.nextNull();
return null; default:
throw new IllegalStateException();
}
} @SuppressWarnings(&quot;unchecked&quot;)
@Override public void write(JsonWriter out, Object value) throws IOException {
if (value == null) {
out.nullValue();
return;
} TypeAdapter&lt;Object&gt; typeAdapter = (TypeAdapter&lt;Object&gt;) gson.getAdapter(value.getClass());
if (typeAdapter instanceof ObjectTypeAdapter) {
out.beginObject();
out.endObject();
return;
} typeAdapter.write(out, value);
}
}

明确一点,Gson将Java中对应的double、long、int都统一为数值类型NUMBER。


/**
* A structure, name or value type in a JSON-encoded string.
*
* @author Jesse Wilson
* @since 1.6
*/
public enum JsonToken { /**
* The opening of a JSON array. Written using {@link JsonWriter#beginArray}
* and read using {@link JsonReader#beginArray}.
*/
BEGIN_ARRAY, /**
* The closing of a JSON array. Written using {@link JsonWriter#endArray}
* and read using {@link JsonReader#endArray}.
*/
END_ARRAY, /**
* The opening of a JSON object. Written using {@link JsonWriter#beginObject}
* and read using {@link JsonReader#beginObject}.
*/
BEGIN_OBJECT, /**
* The closing of a JSON object. Written using {@link JsonWriter#endObject}
* and read using {@link JsonReader#endObject}.
*/
END_OBJECT, /**
* A JSON property name. Within objects, tokens alternate between names and
* their values. Written using {@link JsonWriter#name} and read using {@link
* JsonReader#nextName}
*/
NAME, /**
* A JSON string.
*/
STRING, /**
* A JSON number represented in this API by a Java {@code double}, {@code
* long}, or {@code int}.
*/
NUMBER, /**
* A JSON {@code true} or {@code false}.
*/
BOOLEAN, /**
* A JSON {@code null}.
*/
NULL, /**
* The end of the JSON stream. This sentinel value is returned by {@link
* JsonReader#peek()} to signal that the JSON-encoded value has no more
* tokens.
*/
END_DOCUMENT
}

在调用ObjectTypeAdapter的read()方法时,所有数值类型NUMBER都转换成了Double类型,所以就有了前面出现的问题。到此,我们找到了问题的原因所在。出现这个问题,最根本的是Gson在使用ObjectTypeAdapter解析数值类型时,将其都当Double类型处理,而没有对类型进行细分处理。

解决方法

解决这个问题,大致有两种思路,一是修改NUMBER类型处理的源码,对其进行细化,也就是对ObjectTypeAdapter的read()方法中switch (token)语句进行细化。另一种是自定义一个适合于特定类型的类型适配器,可以参照ObjectTypeAdapter实现,根据前面定义的ResponseData类型,自己实现了一个ResponseData类型适配器ResponseDataTypeAdaptor,代码如下:


public class ResponseDataTypeAdaptor extends TypeAdapter&lt;ResponseData&gt; { public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings(&quot;unchecked&quot;)
@Override
public &lt;T&gt; TypeAdapter&lt;T&gt; create(Gson gson, TypeToken&lt;T&gt; type) {
if (type.getRawType() == ResponseData.class) {
return (TypeAdapter&lt;T&gt;) new ResponseDataTypeAdaptor(gson);
}
return null;
}
}; private final Gson gson; ResponseDataTypeAdaptor(Gson gson) {
this.gson = gson;
} @Override
public void write(JsonWriter out, ResponseData value) throws IOException {
if (value == null) {
out.nullValue();
return;
} out.beginObject();
out.name(&quot;status&quot;);
gson.getAdapter(Integer.class).write(out, value.getStatus());
out.name(&quot;msg&quot;);
gson.getAdapter(String.class).write(out, value.getMsg());
out.name(&quot;data&quot;);
gson.getAdapter(Object.class).write(out, value.getData());
out.endObject();
} @Override
public ResponseData read(JsonReader in) throws IOException {
ResponseData data = new ResponseData();
Map&lt;String, Object&gt; dataMap = (Map&lt;String, Object&gt;) readInternal(in);
data.setStatus((Integer) dataMap.get(&quot;status&quot;));
data.setMsg((String) dataMap.get(&quot;msg&quot;));
data.setData(dataMap.get(&quot;data&quot;));
return data;
} private Object readInternal(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
List&lt;Object&gt; list = new ArrayList&lt;Object&gt;();
in.beginArray();
while (in.hasNext()) {
list.add(readInternal(in));
}
in.endArray();
return list; case BEGIN_OBJECT:
Map&lt;String, Object&gt; map = new LinkedTreeMap&lt;String, Object&gt;();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), readInternal(in));
}
in.endObject();
return map; case STRING:
return in.nextString(); case NUMBER:
String numberStr = in.nextString();
if (numberStr.contains(&quot;.&quot;) || numberStr.contains(&quot;e&quot;)
|| numberStr.contains(&quot;E&quot;)) {
return Double.parseDouble(numberStr);
}
if (Long.parseLong(numberStr) &lt;= Integer.MAX_VALUE) {
return Integer.parseInt(numberStr);
}
return Long.parseLong(numberStr); case BOOLEAN:
return in.nextBoolean(); case NULL:
in.nextNull();
return null; default:
throw new IllegalStateException();
}
}
}

涉及到NUMBER类型处理改动比较简单,如果待处理的原始数据中包含小数点或者是科学表示法则认为是浮点型,否则转化为整型。

实例验证

使用自定义的ResponseDataTypeAdaptor类型适配器,重新解析实例中的json字符串,测试代码如下:


public class GsonTest { @Test
public void test() {
String json = &quot;{\&quot;status\&quot;:200,\&quot;msg\&quot;:\&quot;OK\&quot;,\&quot;data\&quot;:[{\&quot;id\&quot;:1,\&quot;username\&quot;:\&quot;eric\&quot;,\&quot;password\&quot;:\&quot;123456\&quot;,\&quot;age\&quot;:29,\&quot;sex\&quot;:0,\&quot;permission\&quot;:0,\&quot;isDel\&quot;:0}]}&quot;;
Gson gson = buildGson();
ResponseData data = gson.fromJson(json, ResponseData.class);
System.out.println(data.getData());
} private Gson buildGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapterFactory(ResponseDataTypeAdaptor.FACTORY);
return gsonBuilder.create();
}
}

运行结果如下:


{&quot;status&quot;:200,&quot;msg&quot;:&quot;OK&quot;,&quot;data&quot;:[{&quot;id&quot;:1,&quot;username&quot;:&quot;eric&quot;,&quot;password&quot;:&quot;123456&quot;,&quot;age&quot;:29,&quot;sex&quot;:0,&quot;permission&quot;:0,&quot;isDel&quot;:0}]}
[{id=1, username=eric, password=123456, age=29, sex=0, permission=0, isDel=0}] ===============================================
Default Suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

可以发现,结果正确,整型的依然是整型,浮点型依旧为浮点型,问题得到解决。至此,有关Gson格式转换Integer变为Double类型问题原因分析以及解决方案就介绍到这,供大家参考。

原文链接:Gson格式转换Integer变为Double类型问题解决

Gson格式转换Integer变为Double类型问题解决的更多相关文章

  1. pandas 时间格式转换

    OUTLINE 常见的时间字符串与timestamp之间的转换 日期与timestamp之间的转换 常见的时间字符串与timestamp之间的转换 这里说的字符串不是一般意义上的字符串,是指在读取日期 ...

  2. Gson将字符串转map时,int默认为double类型

      gson能够将json字符串转换成map, 但是在转成map时, 会默认将字符串中的int , long型的数字, 转换成double类型 , 数字会多一个小数点 , 如 1 会转成 1.0 Gs ...

  3. FormatUtil类型格式转换

    package cn.edu.hbcf.common.utils; import java.math.BigDecimal; import java.math.BigInteger; import j ...

  4. 使用 google gson 转换Timestamp或Date类型为JSON字符串.

    http://blog.csdn.net/z69183787/article/details/13016289 创建类型适配类: import java.lang.reflect.Type; impo ...

  5. struts2中Double类型的转换

    今天做项目,ssh + Extjs,页面js中定义了几个NumberField,对应的value都是double类型的,其中有个NumberField的name为 name,结果执行的时候报错了,说找 ...

  6. NSString 转换 float 的精度问题, 换double类型可以解决

    @"0.01" 转换成float时, 经常会变成  0.009999799 这种形式, 因为float类型无法精准保存, 系统会选一个接近的值来代替. 而double类型则可以有更 ...

  7. Java中String转换Double类型 Java小数点后留两位

    Java中String转换Double类型 double num1 = 0.0; String qq = "19.987"; num1 = Double.valueOf(qq.to ...

  8. freemark声明变量,boolean,date,date日期格式转换成String类型的(五)

    <br/>assign用来定义变量<#assign name="刘德华"><br/> 获取assign定义变量的值:${name} <br ...

  9. double 类型转化为Integer

    (1)把double先转化成int类型 Double reseve3=Double.parseDouble(bddet[0].getReserve3()); int b=reseve3.intValu ...

随机推荐

  1. 面试题_Spring基础篇

    Spring基础题 1. 什 么 是 Spring? Spring 是 个 java 企 业 级 应 用 的 开 源 开 发 框 架 .Spring 主 要 用 来 开 发 Java 应 用 , 但 ...

  2. mitmproxy修改二级代理

    第一步 mitmweb --mode upstream:http://114.240.101.242:5672 -s server.py 第二步 def request(self, flow: mit ...

  3. moveLeft()

    这里大致都和上面一样,就是在记录左边坐标时,应该应该是lx = x - 1. void moveLeft(){ //定义变量存放人物左边的坐标 int lx, ly; //当左边没有元素时,直接ret ...

  4. sass 插值语句的使用

    定义了一个 px 转 rem 的函数 @function remP($px) { @return $px / (750 / 15) * 1rem; } 在使用 calc 的时候想要使用函数求值的时候遇 ...

  5. CORS和jsonp实现跨域请求

    同源策略:所谓同源是指,域名,协议,端口相同,它是由Netscape提出的一个著名的安全策略,现在所有支持JavaScript 的浏览器都会使用这个策略.当浏览器同时打开两个tab页面(两个不同服务器 ...

  6. [go]redis基本使用

    redis 数据结构操作 import ( "github.com/garyburd/redigo/redis" ) // set c, err := redis.Dial(&qu ...

  7. SdCardUtils

    import android.os.Environment; import android.os.StatFs; public class SdCardUtils { public static bo ...

  8. 表单中使用<button>的注意点

    本文主要记录了我调查问题的思路想法,想看结论的同学直接拖到最后吧 上周在做项目的时候,有一个需求是在页面中加一个按钮,点一下查询数据库将内容填充在表格中.这不是很简单嘛,页面加个按钮,发送ajax请求 ...

  9. Djang之ModelForm组件的简单使用

    ModelForm组件的简单使用 models.py from django.db import models class UserInfo(models.Model): username = mod ...

  10. AP注册

    1.ac发现ap 两种模式:二层发现.三层发现 按ap与ac所处ip网段不同,可以把注册过程分为二层模式和三层模式: 两种模式均通过发送discovery报文进行,二层模式discovery报文仅在同 ...