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()
问题原因
- 未处理字段缺失场景:当JSON中不存在
title、modified_at或duration等字段时,json.get()返回JsonNull,调用asJsonPrimitive会直接抛出IllegalStateException,Gson遇到未捕获的反序列化异常时会跳过当前对象解析。 - 类型转换逻辑不严谨:
formatDuration的处理中,若duration不是数字类型,asDouble会抛出异常;且?.asDouble.toString()在值为null时会得到字符串"null",不符合预期。 - 转换器顺序错误:
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
相关产品推荐
相关产品推荐

