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

Android Retrofit2 搭配kotlinx.serialization不依赖GSON序列化null值方案

根因分析

你遇到的异常是因为kotlinx.serialization默认不支持两种场景:

  1. 顶层JSON值直接为null
  2. 响应体完全为空(EOF)时尝试反序列化,哪怕你声明了类型为可空,默认的Converter也不会自动把空body映射为null

注意:你之前注释的捕获异常匹配错误字符串的方案非常脆弱,序列化库版本更新或者错误文案调整就会失效,不建议在生产环境使用


可行方案

方案一:全局适配(一劳永逸,适合多个接口存在类似场景)

无需修改现有业务代码,只需调整序列化配置和Converter,全局支持可空响应:

  1. 调整全局Json实例配置,开启宽松解析规则:
@Provides
@ExperimentalSerializationApi
fun provideJson(): Json {
    return Json {
        ignoreUnknownKeys = true // 忽略后端返回的未定义字段
        isLenient = true // 宽松解析,兼容不规范的JSON格式
        coerceInputValues = true // 类型不匹配时用属性默认值填充,避免直接崩溃
    }
}
  1. 自定义Converter.Factory,自动将空响应、顶层null转为null:
@ExperimentalSerializationApi
class NullableSerializationConverterFactory(
    private val delegate: Converter.Factory
) : Converter.Factory() {
    override fun responseBodyConverter(
        type: Type,
        annotations: Array<out Annotation>,
        retrofit: Retrofit
    ): Converter<ResponseBody, *>? {
        val delegateConverter = delegate.responseBodyConverter(type, annotations, retrofit)
        return Converter<ResponseBody, Any?> { body ->
            if (body.contentLength() == 0L) {
                null
            } else {
                body.use {
                    try {
                        delegateConverter?.convert(it)
                    } catch (e: JsonDecodingException) {
                        if (e.message?.contains("JSON input: null") == true) null else throw e
                    }
                }
            }
        }
    }
}
  1. 替换原来的ConverterFactory注入逻辑:
@Provides
@ExperimentalSerializationApi
fun provideConverterFactory(json: Json): Converter.Factory {
    val mediaType = "application/json".toMediaType()
    val originalFactory = json.asConverterFactory(mediaType)
    return NullableSerializationConverterFactory(originalFactory)
}

配置完成后原有业务代码完全不需要改动,接口声明Response<SupportConfigurationJson?>就可以正常拿到null值。


方案二:单独适配当前接口(侵入性低,适合仅少数接口为该场景)

不需要改动全局配置,只调整当前接口相关代码:

  1. 修改Api接口返回类型为原始响应体:
interface SupportConfigurationApi {
    @GET("/mobile_api/v1/support/configuration")
    suspend fun getSupportConfiguration(): Response<ResponseBody?>
}
  1. 调整Repository层的解析逻辑:
@Singleton
class SupportConfigurationRepository @Inject constructor(
    private val api: SupportConfigurationApi,
    private val jsonMapper: SupportConfigurationJsonMapper,
    private val json: Json // 注入全局Json实例
) {
    suspend fun getSupportConfiguration(): SupportConfiguration? =
        mapJsonToSupportConfiguration(api.getSupportConfiguration().extractOrThrow())

    private suspend fun mapJsonToSupportConfiguration(
        body: ResponseBody?
    ) = withContext(Dispatchers.Default) {
        val jsonStr = body?.string()?.takeIf { it.isNotBlank() && it != "null" }
        val configJson = jsonStr?.let { json.decodeFromString<SupportConfigurationJson>(it) }
        jsonMapper.mapToSupportSettings(configJson)
    }
}

该方案改动范围小,不会影响其他接口的序列化逻辑,风险更低。


内容的提问来源于stack exchange,提问作者gabez_rules

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 08:36:03