Gson將json字串轉map導致int型被轉換成double的採坑之旅
前言:日常開發中,與json打交道的機會很多,一般物件json轉都不會出現什麼問題,但是json轉物件就有可能出現問題了,今天就來說說json轉map導致int型轉換成double的問題
問題重現
- 之前解決過long型被轉化成科學計數法的問題,所有就拿以前的公用方法,一個泛型工具類
public class MyType<T> { 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,1->1.0、0->0.0,如上圖所示

接下來的操作大家都知道了,藉助於網路平臺,於是乎找到幾種解決方式,細心的我發現有人評論解決他們的問題,看來有戲啊【手動滑稽】
解決方案
1、需要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替換成泛型,然後就不行,一臉矇蔽;(問題暫時擱置一旁)
2、自定義TypeAdapter替代Gson預設的adapter(此處埋下伏筆【偷笑】)解決,自定義TypeAdapter如下:
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?
1、關於泛型這裡就要提到 泛型擦除 ,及泛型只在編譯階段有效,執行時就無效了
- 跟蹤原始碼會發現 TypeAdapter 就已經是一個泛型抽象類了
public abstract class TypeAdapter<T> 複製程式碼
- 我在外層又傳了一次泛型,執行時根本就不認識我傳遞的目標物件型別了
- 在外層直接傳遞目標物件型別,這裡我傳遞的是HashMap<String,Object>,可我完全正確的識別出來

- 所以我這裡的操作完全是符合泛型擦除,所以執行時程式碼根本不認識這是個什麼東西,自然不回你達到我們想要的效果了
2、int轉double,其實這是 Gson 在原始碼中故意為之的,其實不僅是 int , long 也會轉化成 double ,接下來我們去尋找證據
- 跟蹤原始碼,走你 => 過程省略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 ,而反過來則不行。
