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

Retrofit非200/201状态码下提取响应体的简单方法

问题:Retrofit中无法获取401响应的errorBody里的reason字段

场景描述

我使用Compose、Flows、Clean Arch、MVVM和仓库模式,希望实现简单的GET/POST请求,处理加载、成功或错误状态,避免重复编写HTTP异常处理逻辑。但在Retrofit中获取errorBody()时,无法拿到API返回的响应体内容:比如登录请求返回401状态码时,API会返回包含reason字段的JSON:

{
    "reason": "You entered an invalid email or password.",
    "success": false
}

现有代码

fun login(email: String, password: String) {
    _loginState.update {
        it.copy(isLoading = true)
    }

//call api here
viewModelScope.launch {
    authRepository.login(email, password).collectLatest { loginResponse ->

        val responseCode = loginResponse.code()
        if (loginResponse.isSuccessful) {
            val token = loginResponse.body()?.access_token ?: ""
            //val refreshToken = loginResponse.body()?.refresh_token ?: ""
            Log.d("Token:", token)


            localUserManager.saveAccessToken(token)
            _loginState.update {
                it.copy(isLoading = false, loginSuccessful = true, errorMessage = "")
            }


        } else {
            val errorBody = loginResponse.errorBody().toString()
            val message = try {
                JSONObject(errorBody).get("reason")
            } catch (e: JSONException) {
                "Unknown API Error"
            }
            val errorMessage = "Error $responseCode: $message"

            _loginState.update {
                it.copy(
                    isLoading = false,
                    loginSuccessful = false,
                    errorMessage = errorMessage
                )
            }
        }

    }
}

错误原因

你调用了loginResponse.errorBody().toString(),这个方法返回的是Retrofit的ResponseBody对象的内存地址字符串(比如retrofit2.ResponseBody@123456),而不是API返回的实际JSON内容,所以后续用JSONObject解析自然拿不到reason字段。

解决方案

方案1:手动读取errorBody的JSON字符串

修改else分支的代码,先调用errorBody()?.string()获取实际的JSON内容,再解析:

} else {
    val errorMessage = try {
        // 先读取errorBody的字符串内容,注意string()只能调用一次
        val errorBodyString = loginResponse.errorBody()?.string() ?: "Unknown API Error"
        val jsonObject = JSONObject(errorBodyString)
        val reason = jsonObject.getString("reason")
        "Error $responseCode: $reason"
    } catch (e: Exception) {
        // 捕获JSON解析失败或其他异常
        "Error $responseCode: Unknown API Error"
    }

    _loginState.update {
        it.copy(
            isLoading = false,
            loginSuccessful = false,
            errorMessage = errorMessage
        )
    }
}

注意:ResponseBody.string()会消耗响应流,只能调用一次,所以要先把结果存到变量里再使用。

方案2:用Retrofit Converter解析成实体类(更规范)

为API的错误响应创建实体类,用Retrofit的Converter(比如Gson)直接解析,避免手动处理JSON:

  1. 创建错误响应实体类:
data class ApiErrorResponse(
    val reason: String,
    val success: Boolean
)
  1. 修改else分支的解析逻辑:
// 假设你用Gson作为Retrofit的Converter
val gson = Gson()

// 在else块中:
} else {
    val errorMessage = try {
        loginResponse.errorBody()?.let { errorBody ->
            val apiError = gson.fromJson(errorBody.charStream(), ApiErrorResponse::class.java)
            "Error $responseCode: ${apiError.reason}"
        } ?: "Error $responseCode: Unknown API Error"
    } catch (e: Exception) {
        "Error $responseCode: Unknown API Error"
    }

    _loginState.update {
        it.copy(
            isLoading = false,
            loginSuccessful = false,
            errorMessage = errorMessage
        )
    }
}

优化:提取错误处理逻辑(避免重复代码)

如果多个请求都需要处理类似的错误解析,可以把逻辑抽成一个工具函数,不用创建通用类:

fun parseApiError(response: Response<*>, defaultMessage: String = "Unknown API Error"): String {
    return try {
        val errorBodyString = response.errorBody()?.string() ?: return defaultMessage
        val jsonObject = JSONObject(errorBodyString)
        val reason = jsonObject.getString("reason")
        "Error ${response.code()}: $reason"
    } catch (e: Exception) {
        "Error ${response.code()}: $defaultMessage"
    }
}

然后在ViewModel中直接调用:

val errorMessage = parseApiError(loginResponse)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 07:37:34