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

Gson自定义多键/多类型反序列化器失效问题排查

自定义Gson反序列化器导致对象被跳过解析的问题

JSON对象中部分字段存在备选键,且对应的数据类型不一致,需要统一转换为指定类型。自定义反序列化器处理该场景时,出现整个对象被跳过不解析的情况,原因及修复方案如下:

原代码

// model
data class ItemModel(
    val title: String? = "",
    val updateDate: Long? = -1L,
    val formatDuration: String? = "",
)  

// 反序列化器
class ItemModelDeserializer : JsonDeserializer<ItemModel> {
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ItemModel {
        json as JsonObject

        val title = json.get("title").asJsonPrimitive?.asString
        val updateDate = if (json.has("update_date")) json.get("update_date").asJsonPrimitive?.asLong else json.get("modified_at").asJsonPrimitive?.asLong
        val formatDuration = if (json.has("format_duration")) json.get("format_duration").asJsonPrimitive?.asString else json.get("duration").asJsonPrimitive?.asDouble.toString() 

        return ItemModel(title, updateDate, formatDuration)
    }
}

// Retrofit配置
val customGson = GsonBuilder().registerTypeAdapter(ItemModel::class.java, ItemModelDeserializer()).create()

return Retrofit.Builder()
    .baseUrl(baseUrl)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClient)
    .addConverterFactory(BufferedSourceConverterFactory())
    .addConverterFactory(GsonConverterFactory.create(customGson))
    .build()

问题原因

  1. 未处理字段缺失场景:当JSON中不存在title、modified_at或duration等字段时,json.get()返回JsonNull,调用asJsonPrimitive会直接抛出IllegalStateException,Gson遇到未捕获的反序列化异常时会跳过当前对象解析。
  2. 类型转换逻辑不严谨:formatDuration的处理中,若duration不是数字类型,asDouble会抛出异常;且?.asDouble.toString()在值为null时会得到字符串"null",不符合预期。
  3. 转换器顺序错误:BufferedSourceConverterFactory在Gson转换器之前,可能提前消耗了响应流,导致Gson无法获取完整的JSON数据进行解析。

修复方案

1. 修复反序列化器,增加空值与类型安全判断

class ItemModelDeserializer : JsonDeserializer<ItemModel> {
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): ItemModel {
        val jsonObject = json.asJsonObject

        // 安全处理title字段
        val title = if (jsonObject.has("title")) {
            jsonObject.get("title").takeIf { it.isJsonPrimitive }?.asString ?: ""
        } else {
            ""
        }

        // 处理updateDate的备选键,兜底默认值
        val updateDate = when {
            jsonObject.has("update_date") -> jsonObject.get("update_date").takeIf { it.isJsonPrimitive }?.asLong ?: -1L
            jsonObject.has("modified_at") -> jsonObject.get("modified_at").takeIf { it.isJsonPrimitive }?.asLong ?: -1L
            else -> -1L
        }

        // 处理formatDuration的多类型与备选键
        val formatDuration = when {
            jsonObject.has("format_duration") -> jsonObject.get("format_duration").takeIf { it.isJsonPrimitive }?.asString ?: ""
            jsonObject.has("duration") -> {
                val durationElem = jsonObject.get("duration")
                if (durationElem.isJsonPrimitive) {
                    val primitive = durationElem.asJsonPrimitive
                    when {
                        primitive.isString -> primitive.asString
                        primitive.isNumber -> primitive.asDouble.toString()
                        else -> ""
                    }
                } else {
                    ""
                }
            }
            else -> ""
        }

        return ItemModel(title, updateDate, formatDuration)
    }
}

2. 调整Retrofit转换器顺序

将Gson转换器放在BufferedSourceConverterFactory之前,确保Gson能优先处理响应:

return Retrofit.Builder()
    .baseUrl(baseUrl)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create(customGson))
    .addConverterFactory(BufferedSourceConverterFactory())
    .build()

关键注意点

  • 反序列化过程中绝对不能抛出未捕获的异常,否则Gson会直接跳过当前对象(列表场景下会跳过该元素,单个对象场景下返回null)。
  • 所有字段的转换都要增加空值与类型校验,确保任何JSON结构都能被安全解析,返回符合预期的对象。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 20:20:57