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

Retrofit2调用API偶现Expected BEGIN_OBJECT but was STRING错误排查求助

排查Retrofit2 "Expected BEGIN_OBJECT but was STRING" 偶发错误的方案

遇到的错误:

error: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $

这个错误偶发,API调用成功后不再出现,且请求URL一致,错误触发时直接进入onFailure回调,无法查看响应内容。以下是具体排查和解决步骤:

1. 用OkHttp拦截器捕获所有响应内容

不管请求成功还是失败,都能拿到原始响应体,这是定位问题的关键。在构建Retrofit前添加OkHttpClient拦截器:

val gson = GsonBuilder().setLenient().create()
// 构建带拦截器的OkHttpClient
val okHttpClient = OkHttpClient.Builder()
    .addInterceptor { chain ->
        val request = chain.request()
        val response = chain.proceed(request)
        
        // 读取并打印响应内容
        val responseBodyStr = response.body?.string() ?: "无响应内容"
        Log.d("API_Debug", "请求URL: ${request.url}\n响应状态码: ${response.code}\n响应内容: $responseBodyStr")
        
        // 重新构建响应体(因为string()会消耗原始body)
        val newResponseBody = responseBodyStr.toResponseBody(response.body?.contentType())
        response.newBuilder().body(newResponseBody).build()
    }
    .build()

// 构建Retrofit时关联该客户端
val retrofit = Retrofit.Builder()
    .baseUrl("BASE_URL")
    .client(okHttpClient) // 添加这一行
    .addConverterFactory(ScalarsConverterFactory.create()) 
    .addConverterFactory(GsonConverterFactory.create(gson)) 
    .build()

运行后,当错误再次出现时,查看API_Debug日志,就能看到服务端返回的具体字符串内容(可能是错误提示、HTML页面或其他非JSON格式)。

2. 临时修改接口返回类型,手动解析响应

把接口的返回类型从Call<AInfo>改为Call<ResponseBody>,这样可以在onResponse中直接获取原始响应,不受Gson转换失败的影响:

修改AService接口:

interface AService {
    @GET("end_point")
    fun getInfo(
        @Query("serviceKey") serviceKey : String,
        @Query("pageNo") pageNo : String, 
        @Query("numOfRows") numOfRows : String,
        @Query("itemName") itemName : String,
        @Query("efcyQesitm") efcyQesitm : String,
        @Query("type") type: String = "json"
    ): Call<ResponseBody> // 改为ResponseBody
}

修改调用逻辑:

val aCall = aService.getInfo(apiKey, "1","50",aName,"")
aCall.enqueue(object : Callback<ResponseBody>{
    override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
        val responseStr = response.body?.string() ?: ""
        Log.d("API_Response", "响应内容: $responseStr")
        
        // 尝试手动解析为AInfo
        try {
            val aInfo = gson.fromJson(responseStr, AInfo::class.java)
            val aList = aInfo.body?.items
            if (!aList.isNullOrEmpty()) {
                dataList.clear()
                dataList.addAll(aList.filterNotNull())
                adapter.notifyDataSetChanged()
            }
        } catch (e: JsonSyntaxException) {
            // 解析失败,处理异常场景
            Log.e("Parse_Error", "解析响应失败: $responseStr", e)
            // 比如提示用户重试
        }
    }
    override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
        Log.e("API_Request_Failure", "请求失败: ${t.message}", t)
    }
})

这种方式能确保你拿到所有响应内容,即使服务端返回非JSON格式,也能在onResponse中处理。

3. 排查服务端偶发异常的可能

从错误特征来看,大概率是服务端偶尔返回非JSON响应,常见场景包括:

  • 服务端限流/超时,返回错误提示字符串(如"请求过于频繁")
  • 临时维护,返回HTML页面或维护提示
  • 权限验证偶发失败,返回非JSON格式的错误信息
  • 接口内部异常,返回堆栈信息或其他文本

通过第一步的拦截器日志,就能明确服务端返回的内容,进而针对性解决(比如联系后端修复,或在客户端添加异常处理逻辑)。

4. 优化客户端异常处理逻辑

在确认问题原因后,可以添加全局的异常处理:

  • 如果服务端偶尔返回错误字符串,可在拦截器中判断响应内容,提前处理后再交给Gson解析
  • 给AInfo添加兼容逻辑,或者在解析时捕获JsonSyntaxException,并重试请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 20:55:55