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

Android中Kotlin Serialization适配API返回的errors字段(数组/Map)

解决Kotlin Serialization兼容API返回的两种errors结构问题

问题背景

在Android应用中使用Kotlin Serialization + Retrofit解析服务端API时,遇到errors字段存在两种不一致的JSON结构:

  • 无错误时返回空数组:"errors":[]
  • 有错误时返回带动态键的Map对象:"errors":{"kode":"The kode field do not exist."}

直接使用Map<String, String>类型解析时,仅能处理有错误的场景,解析空数组会抛出异常:

Unexpected JSON token at offset 86: Expected start of the object '{', but had '[' instead at path: $.errors

尝试自定义KSerializer但仍触发相同解码异常,需要找到兼容两种结构的解决方案。

解决方案:自定义兼容型KSerializer

核心思路是在解码时先判断JSON token类型,分别处理数组和对象场景,避免依赖异常捕获(也可以捕获异常降级处理)。

优化版自定义Serializer(无异常捕获)

import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonEncoder
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement

object ErrorsSerializer : KSerializer<Map<String, String>> {
    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Errors")

    override fun deserialize(decoder: Decoder): Map<String, String> {
        val jsonDecoder = decoder as JsonDecoder
        val jsonElement = jsonDecoder.decodeJsonElement()
        
        return when (jsonElement) {
            is JsonObject -> jsonDecoder.json.decodeFromJsonElement(jsonElement)
            else -> emptyMap() // 空数组或其他非对象类型,返回空Map
        }
    }

    override fun serialize(encoder: Encoder, value: Map<String, String>) {
        val jsonEncoder = encoder as JsonEncoder
        if (value.isEmpty()) {
            jsonEncoder.encodeJsonElement(JsonArray(emptyList()))
        } else {
            val jsonObj = JsonObject(value.mapValues { JsonPrimitive(it.value) })
            jsonEncoder.encodeJsonElement(jsonObj)
        }
    }
}

在数据类中使用该Serializer

import kotlinx.serialization.Serializable

@Serializable
data class ApiResponse<T>(
    val data: T?,
    @Serializable(with = ErrorsSerializer::class)
    val errors: Map<String, String> = emptyMap()
)

备选方案:异常捕获降级处理

如果不想强依赖JSON解码器类型,也可以通过捕获解码异常的方式实现兼容:

import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.MapSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.ListSerializer

object ErrorsFallbackSerializer : KSerializer<Map<String, String>> {
    private val mapSerializer = MapSerializer(String.serializer(), String.serializer())
    
    override val descriptor: SerialDescriptor = 
        PrimitiveSerialDescriptor("ErrorsFallback", PrimitiveKind.STRING)

    override fun deserialize(decoder: Decoder): Map<String, String> {
        return try {
            decoder.decodeSerializableValue(mapSerializer)
        } catch (_: Exception) {
            emptyMap()
        }
    }

    override fun serialize(encoder: Encoder, value: Map<String, String>) {
        if (value.isEmpty()) {
            // 序列化空Map为数组
            encoder.encodeSerializableValue(ListSerializer(String.serializer()), emptyList())
        } else {
            encoder.encodeSerializableValue(mapSerializer, value)
        }
    }
}

为什么之前自定义Serializer失败?

大概率是因为没有正确处理JSON数组的解码逻辑:要么没有先判断token类型直接尝试解析为Map,要么异常捕获范围不对(比如没有捕获到数组解析的特定异常)。上述优化版Serializer通过先判断JSON元素类型,从根源避免了解码冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 04:05:05