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
相关产品推荐
相关产品推荐

