基于Gson实现API多类型属性的通用序列化方案求助
解决Gson处理"ID/对象"双类型属性的泛型序列化问题
这种API返回同一字段既可以是纯ID值又可以是完整对象的场景确实很常见,我来帮你梳理下解决思路,调整下当前的实现方案:
1. 先调整数据类的设计
你的ObjectType<T>思路是对的,但可以命名更直观,同时符合Kotlin的命名规范,明确它是用来包裹"ID值"或"完整对象"两种情况的容器:
data class ObjectOrId<T>( val id: Long? = null, val value: T? = null ) { // 辅助方法,方便判断当前是哪种类型 fun isIdOnly() = id != null fun isFullObject() = value != null }
2. 编写针对ObjectOrId<T>的反序列化器
你之前的反序列化器目标类型是T,这会导致泛型混淆——因为T本身是你要解析的对象类型,而我们需要反序列化的是包裹它的ObjectOrId<T>。正确的做法是写一个针对这个容器类的反序列化器:
class ObjectOrIdDeserializer<T>(private val targetClass: Class<T>) : JsonDeserializer<ObjectOrId<T>> { override fun deserialize( json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext? ): ObjectOrId<T> { json ?: return ObjectOrId() return when { // 处理纯ID的情况(JSON原始值:数字) json.isJsonPrimitive -> { val id = json.asNumber.toLong() // 兼容Int/Long类型的ID ObjectOrId(id = id) } // 处理完整对象的情况(JSON对象) json.isJsonObject -> { // 用Gson提供的context反序列化,不要重新new Gson,避免丢失全局配置 val fullObject = context?.deserialize(json, targetClass) ObjectOrId(value = fullObject) } else -> throw JsonParseException("无法解析的JSON类型:${json.javaClass.name}") } } }
3. 注册反序列化器并使用
在创建Gson实例时,需要为具体的ObjectOrId<YourTargetClass>类型注册反序列化器。比如假设你的完整对象是Platform:
// 先定义你的目标对象类 data class Platform( val id: Long, val name: String, val slug: String, val url: String, val created_at: Long, val updated_at: Long, val website: String, val alternative_name: String ) // 创建带自定义反序列化器的Gson实例 val gson = GsonBuilder() .registerTypeAdapter( // 生成泛型类型的TypeToken,告诉Gson我们要处理的是ObjectOrId<Platform> TypeToken.getParameterized(ObjectOrId::class.java, Platform::class.java).type, ObjectOrIdDeserializer(Platform::class.java) ) .create()
4. 处理数组场景
对于数组(比如你的platforms字段),直接使用List<ObjectOrId<Platform>>作为解析类型即可,Gson会自动利用我们注册的元素类型反序列化器处理每个元素:
// 解析纯ID数组 val idOnlyPlatforms: List<ObjectOrId<Platform>> = gson.fromJson( """[6]""", TypeToken.getParameterized(List::class.java, ObjectOrId::class.java, Platform::class.java).type ) // 解析完整对象数组 val fullObjectPlatforms: List<ObjectOrId<Platform>> = gson.fromJson( """[{"id": 6, "name": "PC (Microsoft Windows)", "slug": "win", ...}]""", TypeToken.getParameterized(List::class.java, ObjectOrId::class.java, Platform::class.java).type )
为什么之前的方案有问题?
- 你之前的反序列化器目标是
T,但T是具体的业务对象类型,无法同时兼容"数字ID"和"对象"两种场景,泛型边界不清晰; - 直接
Gson().fromJson()会创建新的Gson实例,丢失你全局配置的序列化规则(比如命名策略、其他反序列化器等),应该用方法参数中的context来完成反序列化; - 数组场景不需要单独处理,只要元素类型的反序列化器正确,Gson会自动处理List的解析。
内容的提问来源于stack exchange,提问作者Oreex0
相关产品推荐
相关产品推荐

