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

如何用Gson TypeAdapterFactory适配POKO并优化Retrofit请求?

解决Retrofit+Gson自定义映射嵌套JSON到简化POJO的问题

1. 使用TypeAdapterFactory的read(input: JsonReader)方法是否为正确方案?

完全正确!TypeAdapterFactory就是Gson提供的用于自定义序列化/反序列化逻辑的扩展点,正好适配你这种需要跳过外层包装对象、直接提取内部数组并映射到简化POJO的场景,比一直嵌套POJO要简洁得多,是最优方案之一。


2. 适配TypeAdapterFactory后应选择哪种Retrofit请求方法?

必须选这一个:

fun getTrending(@Query("api_key") apiKey: String): Call<List<Gif>>

原因很简单:

  • Call<Gif>会把整个JSON响应当成单个Gif对象解析,直接会报类型不匹配的错误(外层是对象不是单个Gif);
  • Call<GifSerialList>又回到了你想要避免的嵌套POJO,完全违背了你的优化目标。

选Call<List<Gif>>才能直接拿到你需要的简化数据列表。


3. 如何完善read()方法?解决BEGIN_ARRAY/BEGIN_OBJECT异常

你现在的代码问题出在混用了JsonReader流读取和JsonElement解析,导致Gson的读取指针混乱,从而触发token不匹配的异常。正确的做法是直接用JsonReader的原生API遍历整个JSON结构,不要中途转成JsonElement处理。

这里给你一个修正后的可靠版本:

class GifListTypeAdapterFactory : TypeAdapterFactory {
    override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
        // 只处理List<Gif>类型
        if (type.rawType != List::class.java || type.type !is ParameterizedType) {
            return null
        }
        val elementType = (type.type as ParameterizedType).actualTypeArguments[0]
        if (elementType != Gif::class.java) {
            return null
        }

        return object : TypeAdapter<T>() {
            override fun write(out: JsonWriter, value: T) {
                // 如果不需要序列化Gif列表,直接抛出不支持的异常即可
                throw UnsupportedOperationException("Gif list serialization is not supported")
            }

            override fun read(input: JsonReader): T {
                val gifList = mutableListOf<Gif>()
                input.beginObject() // 进入最外层JSON对象

                while (input.hasNext()) {
                    when (input.nextName()) {
                        "data" -> {
                            input.beginArray() // 进入data数组
                            while (input.hasNext()) {
                                // 逐个解析数组中的每个Gif项
                                val gif = parseSingleGif(input)
                                gifList.add(gif)
                            }
                            input.endArray()
                        }
                        else -> input.skipValue() // 跳过外层其他不需要的字段
                    }
                }
                input.endObject()

                @Suppress("UNCHECKED_CAST")
                return gifList as T
            }

            // 单独提取解析单个Gif对象的逻辑,更清晰
            private fun parseSingleGif(input: JsonReader): Gif {
                var id = ""
                var title = ""
                var importDateTime = ""
                var authorDisplayName: String? = null
                var url = ""

                input.beginObject() // 进入单个Gif的对象
                while (input.hasNext()) {
                    when (input.nextName()) {
                        "id" -> id = input.nextString()
                        "title" -> title = input.nextString()
                        "import_datetime" -> importDateTime = input.nextString()
                        "user" -> {
                            // 处理user字段,可能为null
                            if (input.peek() != JsonToken.NULL) {
                                input.beginObject()
                                while (input.hasNext()) {
                                    if (input.nextName() == "display_name") {
                                        authorDisplayName = input.nextString()
                                    } else {
                                        input.skipValue()
                                    }
                                }
                                input.endObject()
                            } else {
                                input.nextNull()
                            }
                        }
                        "images" -> {
                            // 处理images嵌套对象,提取fixed_height的url
                            input.beginObject()
                            while (input.hasNext()) {
                                if (input.nextName() == "fixed_height") {
                                    input.beginObject()
                                    while (input.hasNext()) {
                                        if (input.nextName() == "url") {
                                            url = input.nextString()
                                        } else {
                                            input.skipValue()
                                        }
                                    }
                                    input.endObject()
                                } else {
                                    input.skipValue()
                                }
                            }
                            input.endObject()
                        }
                        else -> input.skipValue() // 跳过单个Gif对象中其他不需要的字段
                    }
                }
                input.endObject()

                return Gif(id, title, authorDisplayName, importDateTime, url)
            }
        }
    }
}

这个版本严格遵循了JsonReader的token流程:从外层对象→找到data数组→逐个遍历数组项→遍历每个项的嵌套字段,全程没有打乱读取指针,自然不会出现BEGIN_ARRAY/BEGIN_OBJECT不匹配的问题。


4. 如何提取JSON中Gif POKO所需的字段并组合返回?是否需要创建JSON元素列表后再返回?

不需要单独创建JSON元素列表!直接在读取JsonReader流的过程中提取嵌套字段、构建Gif对象即可,这样既高效又避免了额外的内存开销。

上面的代码已经帮你实现了这一点:

  • 遍历到user字段时,进入user对象提取display_name;
  • 遍历到images字段时,进入fixed_height对象提取url;
  • 把所有提取到的字段直接用来构建Gif实例,加入列表后返回。

另外要注意:你的Gif类不需要给嵌套字段加@SerializedName(比如authorDisplayName不需要加@SerializedName("display_name")),因为我们是手动提取的字段,Gson的自动映射规则在这里不生效。


最后别忘了把这个TypeAdapterFactory注册到你的Gson实例里,再配置给Retrofit:

val gson = GsonBuilder()
    .registerTypeAdapterFactory(GifListTypeAdapterFactory())
    .create()

val retrofit = Retrofit.Builder()
    .baseUrl("你的API基础地址")
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:24:53