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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 17:52:52