美文网首页
Gson解析Map数据结构导致的long类型转变成double类

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

作者: 福later | 来源:发表于2020-07-02 18:02 被阅读0次

    先看看下案例代码

            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

    相关文章

      网友评论

          本文标题:Gson解析Map数据结构导致的long类型转变成double类

          本文链接:https://www.haomeiwen.com/subject/dwurqktx.html