使用HashMap<Any>结合Kotlinx Serialization与Retrofit时@Body转换器报错
解决Retrofit + Kotlinx Serialization中HashMap作为@Body的序列化问题
我之前也碰到过一模一样的问题,本质原因是Kotlinx Serialization 默认不支持HashMap<String, Any>这种带有未知类型值的集合——序列化器需要明确知道每个元素的具体类型,而Any是无约束的泛型,哪怕你注册了基本类型的contextual序列化器,也没法覆盖HashMap整体的序列化逻辑。
下面给你几个可行的解决方案,按推荐程度排序:
方案一:用JsonObject替代HashMap(最推荐)
Kotlinx Serialization官方提供了JsonObject(来自kotlinx.serialization.json.JsonObject),它天生就是为动态键值对JSON设计的,自带完整的序列化支持,不用额外写自定义逻辑。
修改接口定义:
@POST("post") suspend fun sendPost(@Body params: JsonObject) : GenericResponse<Post>
构建请求参数:
你可以用buildJsonObject DSL来构造参数,或者把已有的HashMap转成JsonObject:
// 方式1:用DSL构造 val params = buildJsonObject { put("username", "test_user") put("age", 25) put("is_active", true) } // 方式2:从HashMap转换 val hashMap = hashMapOf("key1" to "value1", "key2" to 100) val params = JsonObject(hashMap.mapValues { JsonPrimitive(it.value) })
方案二:自定义HashMap<String, Any>的序列化器
如果你一定要保留HashMap的使用方式,可以给它写一个自定义序列化器,明确处理每个值的类型:
编写自定义序列化器:
import kotlinx.serialization.* import kotlinx.serialization.json.* object StringAnyHashMapSerializer : KSerializer<HashMap<String, Any>> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("HashMap<String, Any>") override fun serialize(encoder: Encoder, value: HashMap<String, Any>) { val jsonEncoder = encoder as JsonEncoder val jsonElements = value.mapValues { (_, v) -> when (v) { is String -> JsonPrimitive(v) is Int -> JsonPrimitive(v) is Double -> JsonPrimitive(v) is Boolean -> JsonPrimitive(v) // 按需扩展其他你需要支持的类型,比如Long、Float等 else -> throw SerializationException("不支持的类型:${v.javaClass.name}") } } jsonEncoder.encodeJsonElement(JsonObject(jsonElements)) } override fun deserialize(decoder: Decoder): HashMap<String, Any> { val jsonDecoder = decoder as JsonDecoder val jsonObject = jsonDecoder.decodeJsonElement() as JsonObject val hashMap = HashMap<String, Any>() jsonObject.forEach { (key, element) -> if (element is JsonPrimitive) { hashMap[key] = when { element.isString -> element.content element.isInt -> element.int element.isDouble -> element.double element.isBoolean -> element.boolean else -> throw SerializationException("无法解析的JsonPrimitive类型") } } else { // 如果需要支持嵌套的JsonObject/JsonArray,这里可以扩展处理逻辑 throw SerializationException("不支持非基本类型的JSON元素") } } return hashMap } }
注册序列化器到SerializersModule:
修改你的Retrofit配置代码,把自定义序列化器注册进去:
fun provideRetrofit(baseUrl : String, okHttpClient: OkHttpClient) : Retrofit { val contentType = "application/json".toMediaType() val json = Json { serializersModule = SerializersModule { contextual(StringAnyHashMapSerializer) } } return Retrofit.Builder() .baseUrl(baseUrl) .client(okHttpClient) .addConverterFactory(json.asConverterFactory(contentType)) .build() }
这样你的原接口定义就可以保留不变了,但要注意HashMap里的值只能是你序列化器支持的类型。
方案三:升级依赖版本(建议配套操作)
你当前使用的依赖版本比较老旧:
- Kotlinx Serialization 1.0.1(当前最新稳定版是1.6.x)
- Retrofit Kotlinx Converter 0.8.0(对应Retrofit 2.8.x,最新版是1.0.0+)
- Kotlin 1.4.10(最新稳定版是1.9.x)
新版本的库修复了很多兼容性问题,对泛型类型的处理也更完善,升级后可能会避免一些奇怪的序列化问题。建议把版本同步升级到匹配的稳定版本,比如:
// Kotlinx Serialization implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0" // Retrofit Converter implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" // Kotlin版本同步到1.9.20(和序列化库版本匹配)
内容的提问来源于stack exchange,提问作者ysfcyln
相关产品推荐
相关产品推荐

