Retrofit+Gson登录报错:Expected BEGIN_OBJECT but was STRING求助
解决Retrofit登录时JSON解析异常问题(Expected BEGIN_OBJECT but was STRING)
错误原因
这个异常的核心问题是服务器返回的响应格式不统一:
- 凭证错误时:返回标准JSON结构(包含
message、errors字段) - 凭证正确时:直接返回纯字符串Token,而非你定义的
LoginResponse对应的JSON对象
Gson转换器试图把纯字符串解析成LoginResponse对象,自然会抛出格式不匹配的异常。
方案一:改用ResponseBody手动处理响应(简单直接)
绕过Gson自动解析,手动处理响应体内容,适配两种返回格式:
- 修改API接口定义,将返回类型改为
Call<ResponseBody>:
interface MyApi { @Headers("Accept:application/json") @POST(Constants.LOGIN_URL) fun login(@Body request : LoginRequest) : Call<ResponseBody> }
- 调用接口时手动判断并解析响应:
apiService.login(loginRequest).enqueue(object : Callback<ResponseBody> { override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { if (response.isSuccessful) { val responseContent = response.body()?.string() ?: "" // 根据内容开头判断是JSON还是纯字符串 if (responseContent.startsWith("{")) { // 解析凭证错误的JSON响应 val errorResp = Gson().fromJson(responseContent, LoginErrorResponse::class.java) // 处理错误逻辑,比如提示用户凭证错误 } else { // 拿到纯字符串Token,按需处理 val authToken = responseContent // 可手动构造LoginResponse对象,或直接使用Token val loginResp = LoginResponse(200, authToken, User("", "", "", "", "")) // 处理登录成功逻辑 } } else { // 处理HTTP错误(如401、500等) } } override fun onFailure(call: Call<ResponseBody>, t: Throwable) { // 处理请求失败逻辑 } })
新增错误响应数据类用于解析凭证错误场景:
data class LoginErrorResponse( @SerializedName("message") var message: String, @SerializedName("errors") var errors: Map<String, List<String>> )
方案二:自定义Gson TypeAdapter自动适配两种格式
如果想保留Call<LoginResponse>的写法,可自定义TypeAdapter让Gson兼容字符串和JSON对象:
- 编写自定义TypeAdapter:
class LoginResponseTypeAdapter : TypeAdapter<LoginResponse>() { private val gson = Gson() override fun write(out: JsonWriter, value: LoginResponse?) { gson.toJson(value, LoginResponse::class.java, out) } override fun read(`in`: JsonReader): LoginResponse { return if (`in`.peek() == JsonToken.STRING) { // 纯字符串Token,构造LoginResponse对象(按需填充字段) val token = `in`.nextString() LoginResponse(200, token, User("", "", "", "", "")) } else { // 正常解析JSON对象 gson.fromJson(`in`, LoginResponse::class.java) } } }
- 在GsonBuilder中注册该TypeAdapter:
private val gson = GsonBuilder() .setLenient() .registerTypeAdapter(LoginResponse::class.java, LoginResponseTypeAdapter()) .create()
- 接口保持原定义,正常调用即可:
apiService.login(loginRequest).enqueue(object : Callback<LoginResponse> { override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) { if (response.isSuccessful) { val loginResp = response.body() // 处理登录成功逻辑,loginResp.authToken即为返回的Token字符串 } else { // 解析错误响应 val errorContent = response.errorBody()?.string() ?: "" val errorResp = Gson().fromJson(errorContent, LoginErrorResponse::class.java) // 处理错误逻辑 } } override fun onFailure(call: Call<LoginResponse>, t: Throwable) { // 处理请求失败逻辑 } })
额外建议
优先和后端沟通,统一返回格式(无论成功失败都返回JSON对象),这是最规范的做法,能避免客户端做额外适配。
内容的提问来源于stack exchange,提问作者Aj Catindig
相关产品推荐
相关产品推荐

