Kotlin泛型扩展Moshi JsonAdapter时出现无适配类异常的解决求助
嘿,这个问题我之前也踩过坑!核心就是泛型类型擦除在搞鬼,Moshi在运行时拿不到具体的类型信息,自然找不到对应的适配器。我来一步步给你拆解清楚:
先复盘下你的场景
你定义了IRunnable接口和它的实现类Cat、Dog,还有负责类型与Map转换的IMapConverter接口,以及针对IRunnable的RunnableConverter实现。原本在RunnableConverter里写getAdapter()方法能正常生成JsonAdapter<IRunnable>,但改成泛型扩展函数toJsonAdapter()后,就抛出了java.lang.IllegalArgumentException: No JsonAdapter for interface moshi.IRunnable异常——确实是类型擦除导致的。
先贴下你的核心代码方便大家理解:
interface IRunnable { fun run() } class Cat : IRunnable { override fun run() { println("cat running") } } class Dog : IRunnable { override fun run() { println("dog running") } } interface IMapConverter<T> { fun getMap(impl : T) : Map<String,String> fun getImpl(map : Map<String , String>) : T? } class RunnableConverter : IMapConverter<IRunnable> { private val key = "runnable" override fun getMap(impl: IRunnable): Map<String, String> { val value = when(impl) { is Cat -> "C" is Dog -> "D" else -> throw RuntimeException("error") } return mapOf(key to value) } override fun getImpl(map: Map<String, String>): IRunnable? { return map[key]?.let { value -> when (value) { "C" -> Cat() "D" -> Dog() else -> throw RuntimeException("error") } } } }
为啥改成泛型扩展函数就炸了?
Kotlin的泛型是编译时类型检查,运行时类型擦除的。当你写fun <T> IMapConverter<T>.toJsonAdapter(): JsonAdapter<T>时,编译后T会被擦除成Any?,Moshi在运行时根本不知道你要处理的是IRunnable类型,自然找不到对应的适配器——它只看到一个模糊的Object类型,当然会报错说找不到接口的JsonAdapter。
而原来在RunnableConverter内部写getAdapter()时,因为这个类明确实现了IMapConverter<IRunnable>,Moshi能通过静态类型分析拿到具体的IRunnable类型,所以能正常生成适配器。
解决办法:给Moshi传递具体的类型令牌
要让Moshi在运行时知道具体的泛型类型,我们需要传递TypeToken(类型令牌),它能保留泛型类型的信息,避免被擦除。这里有两种实现方式:
方式一:扩展函数接收TypeToken参数
修改泛型扩展函数,让它接收TypeToken<T>或者Type类型的参数,这样Moshi就能拿到具体类型了:
import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi import com.squareup.moshi.TypeToken import com.squareup.moshi.Types // 更灵活的版本,接收Type参数 fun <T> IMapConverter<T>.toJsonAdapter(moshi: Moshi, type: Type): JsonAdapter<T> { // 先获取Map的JsonAdapter作为委托 val mapAdapter = moshi.adapter<Map<String, String>>(object : TypeToken<Map<String, String>>() {}) return object : JsonAdapter<T>() { override fun fromJson(reader: JsonReader): T? { val map = mapAdapter.fromJson(reader) return map?.let { getImpl(it) } } override fun toJson(writer: JsonWriter, value: T?) { value?.let { val map = getMap(it) mapAdapter.toJson(writer, map) } } } } // 封装一层,用TypeToken让调用更类型安全 fun <T> IMapConverter<T>.toJsonAdapter(moshi: Moshi, typeToken: TypeToken<T>): JsonAdapter<T> { return toJsonAdapter(moshi, typeToken.type) }
调用的时候,需要传递IRunnable的TypeToken:
val moshi = Moshi.Builder().build() val converter = RunnableConverter() // 传递具体的TypeToken,保留IRunnable的类型信息 val adapter = converter.toJsonAdapter(moshi, object : TypeToken<IRunnable>() {}) // 测试序列化 val cat: IRunnable = Cat() val json = adapter.toJson(cat) println(json) // 输出 {"runnable":"C"} // 测试反序列化 val parsedCat = adapter.fromJson(json) parsedCat?.run() // 输出 "cat running"
方式二:让IMapConverter接口自带类型信息
如果不想每次调用都传TypeToken,可以修改IMapConverter接口,让每个实现类都持有自己的类型信息:
import java.lang.reflect.Type import com.squareup.moshi.TypeToken interface IMapConverter<T> { // 新增属性,让实现类提供具体的类型 val targetType: Type fun getMap(impl : T) : Map<String,String> fun getImpl(map : Map<String , String>) : T? } class RunnableConverter : IMapConverter<IRunnable> { // 实现targetType,用TypeToken保留IRunnable的类型信息 override val targetType: Type = object : TypeToken<IRunnable>() {}.type private val key = "runnable" // 原来的getMap和getImpl实现不变... }
然后扩展函数就可以直接用接口里的targetType,调用更简洁:
fun <T> IMapConverter<T>.toJsonAdapter(moshi: Moshi): JsonAdapter<T> { val mapAdapter = moshi.adapter<Map<String, String>>(object : TypeToken<Map<String, String>>() {}) return object : JsonAdapter<T>() { override fun fromJson(reader: JsonReader): T? { val map = mapAdapter.fromJson(reader) return map?.let { getImpl(it) } } override fun toJson(writer: JsonWriter, value: T?) { value?.let { val map = getMap(it) mapAdapter.toJson(writer, map) } } } } // 使用时不需要再传TypeToken了 val adapter = converter.toJsonAdapter(moshi)
总结
本质就是解决泛型类型擦除的问题,给Moshi提供运行时能识别的具体类型信息。两种方式都能解决问题,你可以根据自己的场景选:如果需要灵活处理不同类型,选方式一;如果每个转换器对应固定类型,方式二更简洁。
内容的提问来源于stack exchange,提问作者smallufo

