如何用Gson反序列化Retrofit返回的不同List类型响应?
这确实是Gson结合Retrofit时很常见的痛点——因为Kotlin/Java的类型擦除机制,List<User>::class.java这种写法根本编译不过,泛型信息在运行时会被抹掉,类字面量只能拿到原始的List::class.java,导致你之前注册的MatchesDeserializer会拦截所有List类型的请求,没法区分是List<User>还是List<Item>。
给你两个更优雅的解决方案,比手动解析JsonObject或者限定死User类型要灵活得多:
方案1:用TypeToken精准注册特定泛型List的TypeAdapter
Gson提供了TypeToken来保留编译时的泛型信息,你可以用它来创建List<User>的具体类型,然后只给这个类型注册自定义解析器:
// 创建List<User>的具体类型实例 val userListType = object : TypeToken<List<User>>() {}.type val retrofit = Retrofit.Builder() .client(client) .baseUrl("$baseURL/") .addConverterFactory( GsonConverterFactory.create( GsonBuilder() .registerTypeAdapter(userListType, MatchesDeserializer()) // 仅匹配List<User> .registerTypeAdapter(Array<String>::class.java, PhotoDeserializer()) .registerTypeAdapter(Triple::class.java, ChatMessagesDeserializer()) .create() ) ) .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) .build()
这样一来,只有返回List<User>的接口会使用你的MatchesDeserializer,而像List<Item>这种不需要自定义解析的泛型List,会自动走Gson的默认反序列化逻辑,完美区分开不同类型的List请求。
方案2:实现TypeAdapterFactory支持多类型泛型List的扩展
如果以后你可能需要给更多泛型List(比如List<Order>、List<Comment>)添加自定义解析器,推荐写一个通用的TypeAdapterFactory,根据List的泛型参数动态选择对应的解析器:
class GenericListTypeAdapterFactory : TypeAdapterFactory { override fun <T : Any?> create(gson: Gson, typeToken: TypeToken<T>): TypeAdapter<T>? { // 先判断当前类型是不是List if (typeToken.rawType != List::class.java) { return null // 不是List,交给Gson默认处理 } // 获取List的泛型参数类型(比如List<User>里的User) val elementType = typeToken.typeParameterTypes[0] // 根据泛型参数返回对应的自定义解析器 return when (elementType) { User::class.java -> MatchesDeserializer() as TypeAdapter<T> // 以后需要添加其他自定义List类型,直接加分支即可 // Order::class.java -> OrderListDeserializer() as TypeAdapter<T> else -> null // 没有匹配的泛型参数,用Gson默认解析 } } }
然后注册这个Factory到Gson里:
val retrofit = Retrofit.Builder() .client(client) .baseUrl("$baseURL/") .addConverterFactory( GsonConverterFactory.create( GsonBuilder() .registerTypeAdapterFactory(GenericListTypeAdapterFactory()) .registerTypeAdapter(Array<String>::class.java, PhotoDeserializer()) .registerTypeAdapter(Triple::class.java, ChatMessagesDeserializer()) .create() ) ) .addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync()) .build()
这种方式扩展性极强,后续新增自定义泛型List类型时,只需要在Factory里加一个分支就行,不用修改Retrofit的初始化代码。
为什么之前的写法不行?
再补充一下核心原因:Java/Kotlin的泛型是编译时语法糖,运行时所有泛型List都会被擦除成原始的List.class,所以直接注册List::class.java的TypeAdapter会拦截所有List类型的请求,没法区分泛型参数。而TypeToken通过匿名内部类的方式,在编译时把泛型信息保留下来,让Gson能精准匹配到特定的泛型List类型。
对比你提到的两种临时方案:手动解析JsonObject太繁琐,而且没法利用Gson的自动解析能力;只处理User类型的Factory其实是方案2的简化版,但方案2的扩展性更好。
内容的提问来源于stack exchange,提问作者urgentx

