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

Retrofit+Gson请求GraphQL如何跳过外层嵌套直接返回目标模型

Retrofit 直接解析GraphQL内层业务模型实现方案

核心思路是跳过固定的外层data节点+接口根字段节点,不需要为每个接口编写多层嵌套的包裹模型类,以下是两种可直接落地的实现:


方案1:通用反序列化适配(推荐,适合GraphQL接口较多的项目)

一次配置后所有GraphQL接口都可以直接返回目标业务模型,改造成本极低。

  • 第一步:定义注解标记每个GraphQL接口对应的根查询字段名
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class GraphQLRoot(val fieldName: String)
  • 第二步:实现请求上下文持有器和拦截器,用来获取当前请求的接口注解信息
// 持有当前请求的Retrofit调用信息
object RetrofitInvocationHolder {
    private val threadLocal = ThreadLocal<Invocation>()
    fun set(invocation: Invocation) = threadLocal.set(invocation)
    fun get(): Invocation = threadLocal.get() ?: throw IllegalStateException("未获取到请求上下文")
    fun remove() = threadLocal.remove()
}

// 拦截器负责在请求发起时存入调用信息,请求结束后清理
class GraphQLInvocationInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
        val request = chain.request()
        val invocation = request.tag(Invocation::class.java)
        invocation?.let { RetrofitInvocationHolder.set(it) }
        return try {
            chain.proceed(request)
        } finally {
            RetrofitInvocationHolder.remove()
        }
    }
}
  • 第三步:实现GraphQL通用反序列化器,自动剥离外层固定节点
class GraphQLDeserializer : JsonDeserializer<Any> {
    override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): Any {
        // 获取当前接口上配置的根字段名
        val method = RetrofitInvocationHolder.get().method()
        val rootAnnotation = method.getAnnotation(GraphQLRoot::class.java)
            ?: throw IllegalArgumentException("GraphQL接口必须添加@GraphQLRoot注解指定根字段")
        // 依次剥离外层data节点、根字段节点
        val dataNode = json.asJsonObject.getAsJsonObject("data")
        val businessNode = dataNode.get(rootAnnotation.fieldName)
        // 直接将内层节点反序列化为目标业务模型
        return Gson().fromJson(businessNode, typeOfT)
    }
}
  • 第四步:修改Retrofit配置,注册自定义逻辑
// 配置OkHttp,添加拦截器
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(GraphQLInvocationInterceptor())
    .build()

// 配置Gson,注册自定义反序列化器
val gson = GsonBuilder()
    .registerTypeHierarchyAdapter(Any::class.java, GraphQLDeserializer())
    .create()

// 初始化Retrofit
val retrofit = Retrofit.Builder()
    .baseUrl("你的服务端地址")
    .client(okHttpClient)
    .addConverterFactory(ScalarsConverterFactory.create())
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build()
  • 第五步:接口定义直接返回目标业务模型,无需编写包裹类
interface GraphQLService {
    @POST("graphql")
    @GraphQLRoot(fieldName = "getCityByName") // 指定当前接口的根字段名
    suspend fun getCityByName(@Body query: String): Response<CityModel>
}

// 业务模型只需要写实际字段,完全不需要外层包裹类
data class CityModel(
    val id: String,
    val name: String,
    val country: String,
    val coord: Coord
)

data class Coord(
    val lon: Double,
    val lat: Double
)

配置完成后直接调用response.body()就能拿到已经解析完成的CityModel实例,没有任何冗余样板代码。


方案2:手动剥离节点(适合GraphQL接口量极少的轻量场景)

如果项目里只有两三个GraphQL接口,不需要做通用适配,直接在拿到字符串响应后手动解析即可,不用改Retrofit全局配置:

suspend fun getCity(queryBody: String): CityModel? {
    val response = postDynamicQuery(queryBody)
    if (!response.isSuccessful) return null
    val rawJson = response.body() ?: return null
    // 手动剥掉外层节点
    val rootElement = JsonParser.parseString(rawJson)
    val cityNode = rootElement.asJsonObject
        .getAsJsonObject("data")
        .getAsJsonObject("getCityByName")
    // 直接解析内层模型
    return Gson().fromJson(cityNode, CityModel::class.java)
}

注意:该方案每个接口都要重复写取节点的逻辑,接口多了维护成本高,优先选择第一种通用方案。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 04:03:23