如何使用Gson TypeAdapter去除嵌套value值并映射至PoJo类?
刚好做过类似的需求,用Gson的TypeAdapter就能完美解决这个嵌套value的映射问题。核心思路就是自定义JSON的解析逻辑,跳过外层的嵌套对象,直接把value字段里的值绑定到PoJo的属性上。下面给你两种方案,按需选择:
方案一:针对特定PoJo的自定义TypeAdapter
这种方案适合只需要处理单个PoJo类的场景,逻辑更直观,也更容易控制细节。
首先先定义你的PoJo数据类:
data class PoJo( val SomeStringProperty: String, val SomeIntProperty: Int, val SomeBooleanProperty: Boolean )
然后写对应的TypeAdapter,手动处理解析和序列化逻辑:
class PoJoTypeAdapter : TypeAdapter<PoJo>() { // 序列化:把PoJo转成带嵌套value的JSON格式(如果不需要序列化可以忽略这个方法) override fun write(out: JsonWriter, value: PoJo) { out.beginObject() // 给每个属性包装成带value的对象 out.name("SomeStringProperty") .beginObject() .name("value").value(value.SomeStringProperty) .endObject() out.name("SomeIntProperty") .beginObject() .name("value").value(value.SomeIntProperty) .endObject() out.name("SomeBooleanProperty") .beginObject() .name("value").value(value.SomeBooleanProperty) .endObject() out.endObject() } // 核心:解析JSON,跳过外层对象直接取value值 override fun read(`in`: JsonReader): PoJo { var stringVal: String? = null var intVal: Int? = null var boolVal: Boolean? = null `in`.beginObject() while (`in`.hasNext()) { when (`in`.nextName()) { "SomeStringProperty" -> { `in`.beginObject() while (`in`.hasNext()) { if (`in`.nextName() == "value") { stringVal = `in`.nextString() } else { // 忽略未知字段,避免解析失败 `in`.skipValue() } } `in`.endObject() } "SomeIntProperty" -> { `in`.beginObject() while (`in`.hasNext()) { if (`in`.nextName() == "value") { intVal = `in`.nextInt() } else { `in`.skipValue() } } `in`.endObject() } "SomeBooleanProperty" -> { `in`.beginObject() while (`in`.hasNext()) { if (`in`.nextName() == "value") { boolVal = `in`.nextBoolean() } else { `in`.skipValue() } } `in`.endObject() } else -> { // 忽略其他未知的JSON字段 `in`.skipValue() } } } `in`.endObject() // 这里可以根据业务需求添加非空校验,避免空指针 return PoJo( stringVal ?: throw JsonSyntaxException("Missing required field: SomeStringProperty.value"), intVal ?: throw JsonSyntaxException("Missing required field: SomeIntProperty.value"), boolVal ?: throw JsonSyntaxException("Missing required field: SomeBooleanProperty.value") ) } }
方案二:通用嵌套Value适配器工厂(批量处理)
如果有很多类都是这种“每个字段嵌套在value对象里”的结构,写一个个TypeAdapter太麻烦,可以用TypeAdapterFactory做通用处理:
class NestedValueAdapterFactory : TypeAdapterFactory { override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? { val elementType = type.type as? Class<T> ?: return null // 跳过基础类型和非自定义类(可选,根据需求调整) if (elementType.isPrimitive || elementType.name.startsWith("java.")) { return null } return object : TypeAdapter<T>() { override fun write(out: JsonWriter, value: T) { // 序列化时自动给每个字段包装成带value的对象 out.beginObject() elementType.declaredFields.forEach { field -> field.isAccessible = true val fieldValue = field.get(value) out.name(field.name) .beginObject() .name("value") gson.toJson(fieldValue, field.type, out) out.endObject() } out.endObject() } override fun read(`in`: JsonReader): T { // 反射创建PoJo实例(注意:需要确保类有无参构造函数,Kotlin data class默认提供) val instance = elementType.getDeclaredConstructor().newInstance() `in`.beginObject() while (`in`.hasNext()) { val fieldName = `in`.nextName() try { val field = elementType.getDeclaredField(fieldName) field.isAccessible = true `in`.beginObject() while (`in`.hasNext()) { if (`in`.nextName() == "value") { // 用Gson自带的解析能力处理字段类型 val fieldValue = gson.fromJson(`in`, field.type) field.set(instance, fieldValue) } else { `in`.skipValue() } } `in`.endObject() } catch (e: NoSuchFieldException) { // 忽略不存在的字段 `in`.skipValue() } } `in`.endObject() return instance } } } }
如何注册使用
不管用哪种方案,都需要把适配器注册到Gson实例中才能生效:
方案一的注册方式
val gson = GsonBuilder() .registerTypeAdapter(PoJo::class.java, PoJoTypeAdapter()) .create()
方案二的注册方式
val gson = GsonBuilder() .registerTypeAdapterFactory(NestedValueAdapterFactory()) .create()
然后就可以正常解析和序列化了:
// 解析JSON val json = """{ "SomeStringProperty": { "value":"StringValue"}, "SomeIntProperty": { "value": 1}, "SomeBooleanProperty": {"value": true} }""" val pojo = gson.fromJson(json, PoJo::class.java) println(pojo) // 输出:PoJo(SomeStringProperty=StringValue, SomeIntProperty=1, SomeBooleanProperty=true) // 序列化(如果需要的话) val serializedJson = gson.toJson(pojo) println(serializedJson) // 回到原来的嵌套结构
注意事项
- 非空校验:上面的代码里加了非空异常,你可以根据业务需求改成默认值或者其他处理方式。
- 未知字段:代码里都做了忽略处理,避免因为JSON多了字段导致解析失败。
- 反射权限:通用方案用到了反射,Kotlin里需要确保字段可访问(
field.isAccessible = true),如果是Android平台,注意API级别限制。
内容的提问来源于stack exchange,提问作者Lena Bru
相关产品推荐
相关产品推荐

