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
相关产品推荐
相关产品推荐

