You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin泛型扩展Moshi JsonAdapter时出现无适配类异常的解决求助

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:10:08