zoukankan      html  css  js  c++  java
  • 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&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类型问题解决

  • 相关阅读:
    HUSTOJ:Transit Tree Path
    HUSTOJ:5500 && 洛谷:P1412:经营与开发
    hdu:2036.改革春风吹满地
    hdu:2030.汉字统计
    Leetcode:338. Bit位计数
    【学习笔记】圆方树(CF487E Tourists)
    BZOJ3238 [Ahoi2013]差异
    CF 187D BRT Contract
    CF 36E Two Paths
    CF 49E Common ancestor
  • 原文地址:https://www.cnblogs.com/datiangou/p/10229922.html
Copyright © 2011-2022 走看看