咨询Retrofit 2中GET请求发送JSON的优雅实现,当前代码失效
解决Retrofit 2中GET请求发送JSON的优雅方式
嘿,这个坑我之前踩过!Retrofit 本身默认不允许给 GET 请求加 @Body 注解——毕竟 HTTP 规范里本来就没定义 GET 请求带请求体的场景,所以你那代码跑不起来太正常了。下面给你几个优雅的解决思路,按需选就行:
1. 将JSON转为字符串作为查询参数传递
这是最简单的方案,把你的 SearchRequest 对象序列化成 JSON 字符串,然后通过 @Query 作为查询参数传递给后端:
接口定义
@GET("products") fun getProducts(@Query("search") searchJson: String): Call<List<Product>>
调用示例
用 Gson 或者 Moshi 把请求对象转成字符串:
val gson = Gson() val searchRequest = SearchRequest(/* 你的参数 */) val searchJson = gson.toJson(searchRequest) apiService.getProducts(searchJson).enqueue(/* 你的回调 */)
⚠️ 注意:这种方式受限于 URL 的长度限制,如果你的 JSON 内容太长,可能会触发后端或者浏览器的长度限制错误。
2. 用OkHttp Interceptor手动给GET请求加请求体
OkHttp 本身是支持 GET 请求带请求体的,只是 Retrofit 做了限制。我们可以写一个自定义拦截器,给特定的 GET 请求加上请求体:
第一步:定义标记注解
先写一个注解,用来标记哪些 GET 请求需要带请求体:
@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class GetWithBody
第二步:实现拦截器
class GetWithBodyInterceptor(private val gson: Gson) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() // 检查当前请求是否标记了GetWithBody注解 val invocation = originalRequest.tag(Invocation::class.java) ?: return chain.proceed(originalRequest) val hasAnnotation = invocation.method().isAnnotationPresent(GetWithBody::class.java) if (hasAnnotation) { // 从请求Tag中获取SearchRequest对象 val searchRequest = originalRequest.tag(SearchRequest::class.java) ?: return chain.proceed(originalRequest) // 序列化为JSON请求体 val requestBody = RequestBody.create( MediaType.parse("application/json; charset=utf-8"), gson.toJson(searchRequest) ) // 构建新的GET请求,带上请求体 val newRequest = originalRequest.newBuilder() .method("GET", requestBody) .build() return chain.proceed(newRequest) } return chain.proceed(originalRequest) } }
第三步:配置Retrofit
把拦截器添加到 OkHttpClient 中:
val gson = Gson() val okHttpClient = OkHttpClient.Builder() .addInterceptor(GetWithBodyInterceptor(gson)) .build() val retrofit = Retrofit.Builder() .baseUrl("你的基础URL") .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .build()
第四步:定义接口
用 @GetWithBody 标记接口,并用 @Tag 传递请求对象:
@GetWithBody @GET("products") fun getProducts(@Tag search: SearchRequest): Call<List<Product>>
这种方式既保留了 GET 请求的语义,又能发送 JSON 请求体,适合后端必须要求 GET 且 JSON 内容较长的场景。
3. 换用POST请求(最省心的规范方案)
其实从 HTTP 规范的角度来说,带请求体的查询操作更适合用 POST 请求。如果后端能配合修改接口,直接把 @GET 改成 @POST,你的原代码就能正常工作了:
@POST("products") fun getProducts(@Body search: SearchRequest): Call<List<Product>>
这是最符合规范、也最省心的方案,很多后端都会支持用 POST 来处理复杂的搜索查询。
内容的提问来源于stack exchange,提问作者Arsene Huot
相关产品推荐
相关产品推荐

