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:
- 创建错误响应实体类:
data class ApiErrorResponse( val reason: String, val success: Boolean )
- 修改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
相关产品推荐
相关产品推荐

