Gson格式转换Integer变为Double类型问题解决
问题描述
在前后端分离的开发模式下,前后端交互通常采用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<T> { /** * 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 <T> TypeAdapter<T> getAdapter(TypeToken<T> type) { TypeAdapter<?> cached = typeTokenCache.get(type == null ? NULL_KEY_SURROGATE : type); if (cached != null) { return (TypeAdapter<T>) cached; } Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get(); boolean requiresThreadLocalCleanup = false; if (threadCalls == null) { threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>(); calls.set(threadCalls); requiresThreadLocalCleanup = true; } // the key and value type parameters always agree FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type); if (ongoingCall != null) { return ongoingCall; } try { FutureTypeAdapter<T> call = new FutureTypeAdapter<T>(); threadCalls.put(type, call); for (TypeAdapterFactory factory : factories) { TypeAdapter<T> candidate = factory.create(this, type); if (candidate != null) { call.setDelegate(candidate); typeTokenCache.put(type, candidate); return candidate; } } throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + 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<Object> { public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { @SuppressWarnings("unchecked") @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { if (type.getRawType() == Object.class) { return (TypeAdapter<T>) 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<Object> list = new ArrayList<Object>(); in.beginArray(); while (in.hasNext()) { list.add(read(in)); } in.endArray(); return list; case BEGIN_OBJECT: Map<String, Object> map = new LinkedTreeMap<String, Object>(); 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("unchecked") @Override public void write(JsonWriter out, Object value) throws IOException { if (value == null) { out.nullValue(); return; } TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) 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<ResponseData> { public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { @SuppressWarnings("unchecked") @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { if (type.getRawType() == ResponseData.class) { return (TypeAdapter<T>) 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("status"); gson.getAdapter(Integer.class).write(out, value.getStatus()); out.name("msg"); gson.getAdapter(String.class).write(out, value.getMsg()); out.name("data"); gson.getAdapter(Object.class).write(out, value.getData()); out.endObject(); } @Override public ResponseData read(JsonReader in) throws IOException { ResponseData data = new ResponseData(); Map<String, Object> dataMap = (Map<String, Object>) readInternal(in); data.setStatus((Integer) dataMap.get("status")); data.setMsg((String) dataMap.get("msg")); data.setData(dataMap.get("data")); return data; } private Object readInternal(JsonReader in) throws IOException { JsonToken token = in.peek(); switch (token) { case BEGIN_ARRAY: List<Object> list = new ArrayList<Object>(); in.beginArray(); while (in.hasNext()) { list.add(readInternal(in)); } in.endArray(); return list; case BEGIN_OBJECT: Map<String, Object> map = new LinkedTreeMap<String, Object>(); 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(".") || numberStr.contains("e") || numberStr.contains("E")) { return Double.parseDouble(numberStr); } if (Long.parseLong(numberStr) <= 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 = "{\"status\":200,\"msg\":\"OK\",\"data\":[{\"id\":1,\"username\":\"eric\",\"password\":\"123456\",\"age\":29,\"sex\":0,\"permission\":0,\"isDel\":0}]}"; 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(); } }
运行结果如下:
{"status":200,"msg":"OK","data":[{"id":1,"username":"eric","password":"123456","age":29,"sex":0,"permission":0,"isDel":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类型问题原因分析以及解决方案就介绍到这,供大家参考。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
尽量避免bug的一些手法
概述 最近参与了几个需求开发,BUG很少,有些需求没BUG,有些才一个BUG,搞的测试人员还发牢骚说, 大佬,你负责的项目,bug都少的可怜,叫俺怎么活? 哈哈,其实测试人员要感谢我才对,因为开发人员的代码质量高了,会极大的提升测试人员测试的速度,因为测试过程中非常顺畅,没啥阻碍的东西。设想一下,如果提测后,代码BUG满天飞,测试人员不断的提BUG单,开发人员不断的修复,一不小心还可能修复出其他BUG来呢,中间还穿插各种各样不必要的讨论,这些都严重影响了测试进度,当然也严重影响了测试人员和开发人员的心情。因此: 最好是在开发阶段就认真起来,把代码写好,以求后续流程的顺畅性。 那么如何做到写代码的时候,尽量避免BUG呢?趁这个机会也跟大家分享一下我的做法。 尽量避免bug的手法 与产品经理和经验丰富的测试人员多沟通 需求阶段 产品经理正式开需求会议之前,一般都会先把需求文档发出来,这个时候,开发人员一定要认真的看并仔细分析,每个细节都要多想想,有疑问的地方及时跟产品经理沟通。另外,看需求的时候,最好跟熟悉业务的测试人员多多沟通,测试人员是对以往需求最清楚的人,能看到其他人看不到的细节。像...
- 下一篇
如何不使用js实现鼠标hover弹出菜单效果
最近看到很多同学在实现鼠标hover弹出菜单的效果时都是用的js代码去实现的,默认给弹出隐藏掉,通过js事件绑定动态的显/隐弹出菜单元素。 <ul> <li>主页</li> <li>新闻</li> <li id="more">更多</li> <div class="menu" id="menu"> <ul> <li>退出登录</li> <li>更改密码</li> </ul> </div> </ul> <style> .menu{ display: none; } </style> <script> window.onload=function(){ var menu=document.getElementById('menu'); var more=document.getElementById('more'); more.addEventListener('m...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7设置SWAP分区,小内存服务器的救世主