前言:日常開發中,與json打交道的機會很多,一般物件json轉都不會出現什麼問題,但是json轉物件就有可能出現問題了,今天就來說說json轉map導致int型轉換成double的問題
問題重現
- 之前解決過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,如上圖所示
接下來的操作大家都知道了,藉助於網路平臺,於是乎找到幾種解決方式,細心的我發現有人評論解決他們的問題,看來有戲啊【手動滑稽】
解決方案
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,而反過來則不行。