kotlin1.8.10问题导致gson报错TypeToken type argument must not contain a type variable

书接上回,https://blog.****.net/jzlhll123/article/details/139302991。
之前我发现gson报错后:
gson在2.11.0给我的kotlin项目代码报错了。

IllegalArgumentException: TypeToken type argument must not contain a type variable

上次解释原因是因为,gson内部有了报错,根本原因就是二级嵌套泛型获取的解析方式问题。而且写下了解决办法和参考代码。 是错误的。
我后来又通过在demo工程中尝试,怎么都无法复现,困扰了我好几天。我又去研究了字节码和反编译。
最后发现,

public class ApiBean {
    public String type;
    public String name;
    public String url;
}

public class CmdBean<T>  { //T就可以传入ApiBean做为二层嵌套泛型
    public String cmdId;
    public long ts;
    public String pro;
    public T apiBean;
}

//二层嵌套解析 //暂时有问题。后面会讲到他没问题
inline fun <reified T> parse(jsonStr: String?): CmdBean<T>? {
    return jsonStr?.fromJson<CmdBean<T>>()
}

//二层嵌套解析 //暂时有问题。后面会讲到他没问题
inline fun <reified T> parseList(jsonStr: String?): List<T>? {
    return jsonStr?.fromJson<List<T>>()
}

//通用函数 //暂时有问题。后面会讲到他没问题
inline fun <reified T> String.fromJson(): T {
    return Globals.gson.fromJson(this, object : TypeToken<T>() {}.type)
}

class Action {
    fun test() {
        val json = """
{"apiBean":{"name":"nameX","type":"typeX","url":"htpps://api.....com/.../..xxx"},"cmdId":"cmdId010102394","pro":"aaaa","ts":1029301291023}
        """.trimIndent()
        val cmd = parse<ApiBean>(json)
        println("cmd $cmd")
    }

    fun testList() {
        val json = """
[{"name":"icon1","type":"type1","url":"https://.......png"},{"name":"icon2","type":"type2","url":"https://..xx.....png"}]
        """.trimIndent()
        val cmd = parseList<ApiBean>(json)
        println("list $cmd")
    }
}

上述代码是很简单的,主要就是有一个嵌套的解析:
parse<ApiBean>(json), parse函数内部,就是jsonStr?.fromJson<CmdBean<T>>()这样嵌套去解析。然后这个函数又经过一个普通的封装函数fromJson来解析。
在本地kotlin版本

 id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
 //也可能写作:
 kotlin = "1.8.10"
 jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

在1.8.10之下:
编译查看class:
请添加图片描述请添加图片描述请添加图片描述由此,我们就能知道为什么会报错了。原因就是字节码写了new出来的匿名内部类是个带T的,所以因为JVM上泛型擦除出现了T,gson的TypeToken就无法解析到真实的类,因此出现了问题。当我反复对比找各种原因,最后终于怀疑到了版本上。修改到kotlin1.8.20,kotlin1.9.0, kotlin 1.9.24均发现ok 了。对比的图如下:请添加图片描述
请添加图片描述
请添加图片描述
很明显,对于这种嵌套型的inline+reified从kotlin1.8.20开始,生成的内部类就不会再有问题。

并且,gson官方文档提到的解决方案:

Use TypeToken.getParameterized(...), for example TypeToken.getParameterized(List.class, elementType) where elementType is a type you have to provide separately.
For Kotlin users: Use reified type parameters, that means change <T> to <reified T>, if possible. If you have a chain of functions with type parameters you will probably have to make all of them reified.

其实对于kotlin项目而言,

inline fun <reified T> String.fromJson(): T {
    return Globals.gson.fromJson(this, object : TypeToken<T>() {}.type)
}

这个函数就是完美的,
你可以直接传入fromJson<List<Bean>(), 可以传入fromJson<CmdBean<InnerBean>()的形式,他就能给你处理好。
而1.8.10的kotlin,在inline+reified嵌套后就会出现问题。
大于1.8.10的kotlin版本,则可以嵌套传导正确,就不会出现问题了。

而非kotlin的java项目,你就不得不按照官方

public static <T> T parse(String jsonStr, Class<T> t) {
    return new GsonBuilder().create().fromJson(jsonStr, t);
}

public static <T, T2> MyCmd<T2> parseLv2(String jsonStr, Class<T> t, Class<T2> t2) {
    Type typeToken = TypeToken.getParameterized(t, t2).getType();
    return new GsonBuilder().create().fromJson(jsonStr, typeToken);
}

或者参考我上篇帖子https://blog.****.net/jzlhll123/article/details/139302991的三个解决方案。
java中就不会遇到kotlin编译内部类的问题了。而是可能会犯低级错误:
比如

        var json = """
{"apiBean":{"name":"nameX","type":"typeX","url":"htpps://api.....com/.../..xxx"},"cmdId":"cmdId010102394","pro":"aaaa","ts":1029301291023}
        """;
        var cmd = parse(json, CmdBean.class); //错误,没有提供二级泛型。需要TypeToken.getParameterized等方式解析。
        System.out.printf("cmd " + cmd);

		val listCmd = parse(json, List.class) //错误,没有提供二级泛型。需要TypeToken.getParameterized等方式解析。

而kotlin,由于inline和reified就可以解决它,前提是你要注意kotlin的版本哟,1.8.10有坑!1.8.20开始好的。

上一篇:神经网络基础


下一篇:tailwindcss 最简单的使用方式