Json转Map的后出现ClassCastException

运行错误

测试代码

1
2
3
4
5
6
7
8
9
10
import com.google.gson.Gson
fun main() {
val map = HashMap<String, Int>()
map["key1"] = 111111
val jsonString = Gson().toJson(map)
println(map::class.java)
val hashMap = Gson().fromJson(jsonString, map::class.java)
val key1:Int? = hashMap["key1"]
println(key1)
}

运行的时候,会抛类型转换错误

1
2
3
4
class java.util.HashMap
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
at MainTestKt.main(MainTest.kt:9)
at MainTestKt.main(MainTest.kt)

源码分析

追踪调用栈,发现Gson里面处理number的逻辑,走到 MapTypeAdapterFactory 里面

1
2
3
4
5
6
7
read:161, MapTypeAdapterFactory$Adapter (com.google.gson.internal.bind)
read:145, MapTypeAdapterFactory$Adapter (com.google.gson.internal.bind)
fromJson:888, Gson (com.google.gson)
fromJson:853, Gson (com.google.gson)
fromJson:802, Gson (com.google.gson)
fromJson:774, Gson (com.google.gson)
main:8, MainTestKt
  • com.google.gson.internal.bind.ObjectTypeAdapter#read
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    @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();
    }
    }
    上面👆🏻源码可以看到,这里只针对处理了STRING,NUMBER,BOOLEAN,NULL的4中类型,其中对NUMBER类型,都是用Double去接受的。为什么编译的时候OK?

类型擦除

所有的泛型都是编译期间概念,在编译之后,运行的时候,是感知不到泛型的。泛型在编译的时候被擦除了,也就是输出class name的时候,还是map本身。泛型会在编译的时候,做检查。例如我们用double去接受,编译器会抛错,尽管运行的时候是double类型。
image.png
编译之后再反编译回来, 运行时的逻辑就是这样的,并没有泛型信息。可以看到这里有个强转,而Integer是包装类型,不是基本类型,无法强转,导致抛错。

1
2
3
4
5
6
7
8
9
10
public static final void main() {
HashMap map = new HashMap();
((Map)map).put("key1", 111111);
String jsonString = (new Gson()).toJson(map);
Class var2 = map.getClass();
System.out.println(var2);
HashMap hashMap = (HashMap)(new Gson()).fromJson(jsonString, map.getClass());
Integer key1 = (Integer)hashMap.get("key1");
System.out.println(key1);
}

JS number类型

JSON(JavaScript Object Notation, JS对象简谱),和其他语言有多种数值类型不同,int/longint/float/double等,JS 不分整数和浮点型,所有数字都使用浮点型来储存。

总结

可以成功运行

1
2
3
4
5
6
7
8
9
fun main() {
val map = HashMap<String, Int>()
map["key1"] = 111111
val jsonString = Gson().toJson(map)
println(map::class.java)
val hashMap = Gson().fromJson(jsonString, map::class.java)
val key1:Any? = hashMap["key1"]
println(key1)
}
1
2
3
4
class java.util.HashMap
111111.0

Process finished with exit code 0

我的理解是Gson和Json 表示对齐,默认number类型也是用double去解析的,当一个string json转换成map的时候,是感知不到泛型的,实际上的map是map<string,object>类型,而写代码的时候存在泛型过了编译,导致运行的时候抛错