Gson解析Map数据结构导致的long类型转变成double类

2020-07-02  本文已影响0人  福later

先看看下案例代码

        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

上一篇下一篇

猜你喜欢

热点阅读