Gson將json字符串轉map致使int型被轉換成double的採坑之旅

前言:平常開發中,與json打交道的機會不少,通常對象json轉都不會出現什麼問題,可是json轉對象就有可能出現問題了,今天就來講說json轉map致使int型轉換成double的問題json

問題重現

  • 以前解決過long型被轉化成科學計數法的問題,全部就拿之前的公用方法,一個泛型工具類
public class MyType<T> {
    public T gsonToMap(String strJson) {
        return new Gson().fromJson(strJson, new TypeToken<T>() {
        }.getType());
    }
}

String json = "{\"identifier\":\"18111111111\",\"opType\":1,\"platform\":0}";
Map<String, Object> map = new MyType<Map<String, Object>>().gsonToMap(json);
複製代碼
  • 直接將需求類型對象傳入泛型就行了。
  • 然而事與願違,int成功的轉換成double,1->1.0、0->0.0,如上圖所示

接下來的操做你們都知道了,藉助於網絡平臺,因而乎找到幾種解決方式,細心的我發現有人評論解決他們的問題,看來有戲啊【手動滑稽】bash

解決方案

一、須要gson解析的類型 , 重寫他的deserialize方法, 就是將其中json手動解析成map , 不對數據進行處理網絡

public HashMap<String,Object> gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(
                new TypeToken<HashMap<String,Object>>(){}.getType(),
                        new JsonDeserializer<HashMap<String, Object>>() {
                            @Override
                            public HashMap<String, Object> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

                                HashMap<String, Object> hashMap = new HashMap<>();
                                JsonObject jsonObject = json.getAsJsonObject();
                                Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                                for (Map.Entry<String, JsonElement> entry : entrySet) {
                                    hashMap.put(entry.getKey(), entry.getValue());
                                }
                                return hashMap;
                            }
                        }).create();

        return gson.fromJson(strJson, new TypeToken<HashMap<String,Object>>() {
        }.getType());
    }
複製代碼
  • 通過實踐,是能夠轉化成功,可是本着複用的思想,我把map替換成泛型,而後就不行,一臉矇蔽;(問題暫時擱置一旁)

二、自定義TypeAdapter替代Gson默認的adapter(此處埋下伏筆【偷笑】)解決,自定義TypeAdapter以下:ide

public class MapTypeAdapter extends TypeAdapter<Object> {

    private final TypeAdapter<Object> delegate = new Gson().getAdapter(Object.class);

    @Override
    public Object read(JsonReader in) throws IOException {
        JsonToken token = in.peek();
        switch (token) {
            case BEGIN_ARRAY:
                List<Object> list = new ArrayList<>();
                in.beginArray();
                while (in.hasNext()) {
                    list.add(read(in));
                }
                in.endArray();
                return list;

            case BEGIN_OBJECT:
                Map<String, Object> map = new LinkedTreeMap<>();
                in.beginObject();
                while (in.hasNext()) {
                    map.put(in.nextName(), read(in));
                }
                in.endObject();
                return map;
                
            case STRING:
                return in.nextString();

            case NUMBER:
                /**
                 * 改寫數字的處理邏輯,將數字值分爲整型與浮點型。
                 */
                double dbNum = in.nextDouble();

                // 數字超過long的最大值,返回浮點類型
                if (dbNum > Long.MAX_VALUE) {
                    return String.valueOf(dbNum);
                }

                // 判斷數字是否爲整數值
                long lngNum = (long) dbNum;
                if (dbNum == lngNum) {
                    return String.valueOf(lngNum);
                } else {
                    return String.valueOf(dbNum);
                }

            case BOOLEAN:
                return in.nextBoolean();

            case NULL:
                in.nextNull();
                return null;

            default:
                throw new IllegalStateException();
        }
    }

    @Override
    public void write(JsonWriter out, Object value) throws IOException {
        delegate.write(out,value);
    }
}
複製代碼
  • 而後如法炮製,仍然執拗的使用泛型,並將咱們自定義的註冊到gson上
public T gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(new TypeToken<T>(){}.getType(),new MapTypeAdapter()).create();
        return gson.fromJson(strJson, new TypeToken<T>() {
        }.getType());
    }
    
String json = "{\"identifier\":\"18111111111\",\"opType\":1,\"platform\":0}";
Map<String, Object> map = new MyType<Map<String, Object>>().gsonToMap(json);
複製代碼
  • 等待結果中...,每錯就是這麼刺激,int同樣會轉化成double

  • 把泛型直接替換成目標對象類型,再試了試,證實是沒問題的
public static Map<String, Object> gsonToMap(String strJson) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(new TypeToken<Map<String,Object>>(){}.getType(),new MapTypeAdapter()).create();
        return gson.fromJson(strJson, new TypeToken<Map<String, Object>>() {
        }.getType());
    }
    
String json = "{\"identifier\":\"18111111111\",\"opType\":1,\"platform\":0}";
Map<String, Object> map = new MyType<Map<String, Object>>().gsonToMap(json);
複製代碼

上述方案的確是能夠解決個人問題,可是卻給我留下了疑問;本着知其然知其因此然的目的,以爲解決這些疑惑工具

解決疑惑

  • 爲何傳遞泛型不行?
  • 爲何是把int轉化成了double,而不是其餘類型好比string?

一、關於泛型這裏就要提到 泛型擦除,及泛型只在編譯階段有效,運行時就無效了ui

  • 跟蹤源碼會發現 TypeAdapter 就已是一個泛型抽象類了
public abstract class TypeAdapter<T>
複製代碼
  • 我在外層又傳了一次泛型,運行時根本就不認識我傳遞的目標對象類型了
  • 在外層直接傳遞目標對象類型,這裏我傳遞的是HashMap<String,Object>,可我徹底正確的識別出來

  • 因此我這裏的操做徹底是符合泛型擦除,因此運行時代碼根本不認識這是個什麼東西,天然不回你達到咱們想要的效果了

二、int轉double,其實這是Gson在源碼中故意爲之的,其實不只是int,long也會轉化成double,接下來咱們去尋找證據this

  • 跟蹤源碼,走你 => 過程省略1000步,忽略1000000字,咱們會來到Gson下的這個地方
  • 這裏處理Number型的adapter,除此以外還有
處理double:
private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
    if (serializeSpecialFloatingPointValues) {
      return TypeAdapters.DOUBLE;
    }
    return new TypeAdapter<Number>() {
      @Override public Double read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
          in.nextNull();
          return null;
        }
        return in.nextDouble();
      }
      @Override public void write(JsonWriter out, Number value) throws IOException {
        if (value == null) {
          out.nullValue();
          return;
        }
        double doubleValue = value.doubleValue();
        checkValidFloatingPoint(doubleValue);
        out.value(value);
      }
    };
  }
  
  處理float:
  private TypeAdapter<Number> floatAdapter(boolean serializeSpecialFloatingPointValues) {
    if (serializeSpecialFloatingPointValues) {
      return TypeAdapters.FLOAT;
    }
    return new TypeAdapter<Number>() {
      @Override public Float read(JsonReader in) throws IOException {
        if (in.peek() == JsonToken.NULL) {
          in.nextNull();
          return null;
        }
        return (float) in.nextDouble();
      }
      @Override public void write(JsonWriter out, Number value) throws IOException {
        if (value == null) {
          out.nullValue();
          return;
        }
        float floatValue = value.floatValue();
        checkValidFloatingPoint(floatValue);
        out.value(value);
      }
    };
  }
複製代碼
  • 其實這裏就是在尋找與咱們目標對象想匹配的類型,可是若是找不到相匹配的類型,就會去調用 ObjectTypeAdapter,繼續跟蹤,它終於要在這裏正式尋找喜歡的適配器了【斜眼笑】

  • 咋們運氣比較好,這 for (TypeAdapterFactory factory : factories) 裏有40幾個適配器,第二個就是咱們尋找的 ObjectTypeAdapter
  • 它一看你們都是 T 就你跟我長得最像了,那就調用你了,因而乎就來到新世界
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);
  }
}
複製代碼
  • 是否是跟咱們以前自定義的adapter如出一轍,這就是爲何咱們要複寫這個TypeAdapter,重點看下面
case NUMBER:
      return in.nextDouble();
複製代碼
  • 只要是Number(包括int、long、float、double等)型,都會被強制轉化成double,至於爲何這麼作,由於這裏全部的類型均可以轉換成double,而反過來則不行。

其實,還有一個更簡單的方法,那就是不用Gson,好比使用FastJson,徹底不會出現這個問題

後記:解決問題不是根本,須要尋找問題產生的根本,從根源上杜絕它的發生,這是之後須要多多增強的地方。加油(若有紕漏,歡迎指教)!!!

相關文章
相關標籤/搜索