如何用Gson TypeAdapterFactory适配POKO并优化Retrofit请求?
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

