Gson解析Map数据结构导致的long类型转变成double类
先看看下案例代码
Gson gson = new GsonBuilder().serializeNulls().create();
Map dataSrc = new HashMap() ;
dataSrc.put("1", 23232.4) ;
dataSrc.put("2", 23123432L) ;
String mapString = gson.toJson(dataSrc) ;
System.out.println(mapString);
Map descMap = gson.fromJson(mapString, Map.class) ;
Object object = descMap.get("2") ;
Long dd = (Long)object ;
System.out.println(dd);
看下打印结果
{"1":23232.4,"2":23123432}
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Long
at GsonTest.main(GsonTest.java:22)
代码中明明put是long类型啊,怎么报java.lang.Double cannot be cast to java.lang.Long,为了继续验证,我们先按编译器提示改下代码
Gson gson = new GsonBuilder().serializeNulls().create();
Map dataSrc = new HashMap() ;
dataSrc.put("1", 23232.4) ;
dataSrc.put("2", 23123432L) ;
String mapString = gson.toJson(dataSrc) ;
System.out.println(mapString);
Map descMap = gson.fromJson(mapString, Map.class) ;
Object object = descMap.get("2") ;
Double dd = (Double)object ;
System.out.println(dd);
再来看下打印结果
{"1":23232.4,"2":23123432}
2.3123432E7
23123432L 变成了2.3123432E7 如我们标题,long类型变成了double类型
为什么
需要准备的知识点
1.泛型
2.Gson解析原理
3.double是如何在计算机中存储的
先简单讲讲泛型吧,我们这里只讲涉及到本案例分析的知识点;我们先看看Map类
public interface Map<K,V> {
// Query Operations
...
...
}
K,V 是类类型参数的通配符,在编译后会被擦除,也就是说这东西JVM压根见不到,那么它有什么用呢,简单点讲,就是K,V 在编译后会被其他Class 类型替换,比如
Map<String,String> map = new HashMap<String,String>() ;
这时候K,V代表着String.class 类类型;如果这样写呢
Map map = new HashMap() ;
这时候K,V代表着Objcect.class 类类型,至于原因,可以参看泛型类擦除的边界确认问题。
2.3123432E7 怎么来的
double 属于双精度浮点数
浮点数是按照整数部分,小数部分,指数部分存放在计算类的
符号位 (Sign):0代表正数,1代表为负数;
指数位 (Exponent):用于存储科学计数法中的指数数据;
尾数部分 (Mantissa):采用移位存储尾数部分;
double的第一位是符号位,接下来11位存储的是指数位,再接下来存的是尾数部分,2.3123432E7中的符号位为0,指数位为7(10进制),尾数位为3123432(10进制)详细的可参看double是如何在计算机中存储
目前通过上面分析至少我们知道2.3123432E7 是一个double类型的数据,其实在这里还是没讲清楚2.3123432E7怎么来的,有兴趣的可以研究下doubleToRawLongBits这个方法,这里我点到为止;
到目前为止,我们还只是说明了一个问题:long类型确实转变成double类型
要解开这个问题,不得不深入源码一探究竟了,我这里不打算从宏观方面讲解Gson的工作原理及流程,我们还是关注涉及本案例的类和代码,想弄清楚Gson解析原理的可自行研究或者参看Gson的反射解析机制详解.
来吧,源码读起,源码涉及到以下类
Gson.java,TypeAdapter.java,
从案例中打印的结果可知,发生这种转变时机并不是在将Map转换成json 的时候,那肯定就是在将json转化成Map的时候,我们来看看案例中的fromJson方法,最终会调用
Gson.java中
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) {
/*
* For compatibility with JSON 1.5 and earlier, we return null for empty
* documents instead of throwing.
*/
if (isEmpty) {
return null;
}
throw new JsonSyntaxException(e);
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
} catch (IOException e) {
// TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
throw new JsonSyntaxException(e);
} catch (AssertionError e) {
throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);
} finally {
reader.setLenient(oldLenient);
}
}
着重关注
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
TypeAdapter 可以说是Gson的核心设计了,Gson设计模式包含了适配器设计模式,工厂模式,TypeAdapter 是一个抽象类,提供了读写方法,也就是完成不同数据结构与json的相互转化,Gson提供了多个TypeAdapter 的实现类,不同的实现类肩负着不同的数据结构与Json的相互转化的职能,在这里Map这种数据结构对应着MapTypeAdapterFactory,也就是最终的解析数据在
MapTypeAdapterFactory.java 的方法中
@Override public Map<K, V> read(JsonReader in) throws IOException {
JsonToken peek = in.peek();
if (peek == JsonToken.NULL) {
in.nextNull();
return null;
}
Map<K, V> map = constructor.construct();
if (peek == JsonToken.BEGIN_ARRAY) {
in.beginArray();
while (in.hasNext()) {
in.beginArray(); // entry array
K key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
in.endArray();
}
in.endArray();
} else {
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
V replaced = map.put(key, value);
if (replaced != null) {
throw new JsonSyntaxException("duplicate key: " + key);
}
}
in.endObject();
}
return map;
}
通过上面代码map.put(key,value)可知,最终的赋值与
K key = keyTypeAdapter.read(in);
V value = valueTypeAdapter.read(in);
这两句代码有关,好,继续跟踪,穿过层层包装,keyTypeAdapter.read(in) 最终会调用到ObjectTypeAdapter 中的read方法
ObjectTypeAdapter .java
@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();
}
}
通读这个方法,Long类型的数据对应着 case NUMBER: 这个分支逻辑,再看看这个分支逻辑下的代码
case NUMBER:
return in.nextDouble();
看到没有,double,凶手终于出现了。至此,我们已经一步一步(大步)的解密了Gson是如何将存入Map中的long数据类型转换成double类型的,其实这里不但是long类型会被转变成double类型,Int类型,short,byte 都会,可见多么危险的操作。
总结
其实像这种情况还是蛮常见的,特别是前后台没有严格规范数据类型的时候,特别容易出现类型案例,在使用Map与Json相互转化时应该如何避免,其实也简单,就是做数据类型约束,如以上案例代码可以改造成
Gson gson = new GsonBuilder().serializeNulls().create();
Map<String,String> dataSrc = new HashMap() ;
dataSrc.put("1", "23232.4") ;
dataSrc.put("2", "23123432L") ;
String mapString = gson.toJson(dataSrc) ;
System.out.println(mapString);
Map<String,String> descMap = gson.fromJson(mapString, Map.class) ;
String str= descMap.get("2") ;
System.out.println(str);
打印
{"1":"23232.4","2":"23123432L"}
23123432L