Retrofit 2.0如何获得反序列化的错误响应.body

我正在使用改装2.0.0-beta1

在测试中,我有另一个场景,预计会出现错误HTTP400

我想要retrofit.Response<MyError> response。 但response.body() == null

我的错误没有被反序列化-我只在这里看到它

response.errorBody().string()

但它不会将MyError作为对象提供给我

206402 次浏览

通过以下方式解决:

Converter<MyError> converter =
(Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError =  converter.fromBody(response.errorBody());

在Retrofit 2.0 Beta2中,这是我获得错误响应的方式:

  1. 同步的

    try {
    Call<RegistrationResponse> call = backendServiceApi.register(data.in.account, data.in.password,
    data.in.email);
    Response<RegistrationResponse> response = call.execute();
    if (response != null && !response.isSuccess() && response.errorBody() != null) {
    Converter<ResponseBody, BasicResponse> errorConverter =
    MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
    BasicResponse error = errorConverter.convert(response.errorBody());
    //DO ERROR HANDLING HERE
    return;
    }
    RegistrationResponse registrationResponse = response.body();
    //DO SUCCESS HANDLING HERE
    } catch (IOException e) {
    //DO NETWORK ERROR HANDLING HERE
    }
    
  2. Asynchronous

    Call<BasicResponse> call = service.loadRepo();
    call.enqueue(new Callback<BasicResponse>() {
    @Override
    public void onResponse(Response<BasicResponse> response, Retrofit retrofit) {
    if (response != null && !response.isSuccess() && response.errorBody() != null) {
    Converter<ResponseBody, BasicResponse> errorConverter =
    retrofit.responseConverter(BasicResponse.class, new Annotation[0]);
    BasicResponse error = errorConverter.convert(response.errorBody());
    //DO ERROR HANDLING HERE
    return;
    }
    RegistrationResponse registrationResponse = response.body();
    //DO SUCCESS HANDLING HERE
    }
    
    
    @Override
    public void onFailure(Throwable t) {
    //DO NETWORK ERROR HANDLING HERE
    }
    });
    

Update for Retrofit 2 beta3:

  1. Synchronous - not changed
  2. Asynchronous - Retrofit parameter was removed from onResponse

    Call<BasicResponse> call = service.loadRepo();
    call.enqueue(new Callback<BasicResponse>() {
    @Override
    public void onResponse(Response<BasicResponse> response) {
    if (response != null && !response.isSuccess() && response.errorBody() != null) {
    Converter<ResponseBody, BasicResponse> errorConverter =
    MyApplication.getRestClient().getRetrofitInstance().responseConverter(BasicResponse.class, new Annotation[0]);
    BasicResponse error = errorConverter.convert(response.errorBody());
    //DO ERROR HANDLING HERE
    return;
    }
    RegistrationResponse registrationResponse = response.body();
    //DO SUCCESS HANDLING HERE
    }
    
    
    @Override
    public void onFailure(Throwable t) {
    //DO NETWORK ERROR HANDLING HERE
    }
    });
    

对于使用retrofit 2.0-beta2的异步调用,我是这样做的:

@Override
public void onResponse(Response<RegistrationResponse> response,
Retrofit retrofit) {
if (response.isSuccess()) {
// Do success handling here
} else {
try {
MyError myError = (MyError)retrofit.responseConverter(
MyError.class, MyError.class.getAnnotations())
.convert(response.errorBody());
// Do error handling here
} catch (IOException e) {
e.printStackTrace();
}
}
}

我目前使用一个非常简单的实现,它不需要使用转换器或特殊的类。我使用的代码如下:

public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
DialogHelper.dismiss();


if (response.isSuccessful()) {
// Do your success stuff...
} else {
try {
JSONObject jObjError = new JSONObject(response.errorBody().string());
Toast.makeText(getContext(), jObjError.getJSONObject("error").getString("message"), Toast.LENGTH_LONG).show();
} catch (Exception e) {
Toast.makeText(getContext(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
}

这里需要注意的一点是,response.errorBody().string()只会返回一次正确的值。如果您再次调用它,它将返回一个空字符串。因此,如果您想重用它,请在第一次调用时将值存储在变量中。

有一种方法可以从响应中获取错误主体字符串,而不会在下一次调用时将其置空,方法是滚动您自己的toString()的实现,该实现不会更新errorBody缓冲区的读取指针。 有关详细信息,请参阅这个答案

https://stackoverflow.com/a/21103420/2914140https://futurestud.io/tutorials/retrofit-2-simple-error-handling中,显示了改型2.1.0的该变型。

call.enqueue(new Callback<MyResponse>() {
@Override
public void onResponse(Call<MyResponse> call, Response<MyResponse> response) {
if (response.isSuccessful()) {
...
} else {
Converter<ResponseBody, MyError> converter
= MyApplication.getRetrofit().responseBodyConverter(
MyError.class, new Annotation[0]);
MyError errorResponse = null;
try {
errorResponse = converter.convert(response.errorBody());
} catch (IOException e) {
e.printStackTrace();
}
}
}

这似乎是使用OKHttp和Retrofit时的问题,因此您可以删除OKHttp或使用下面的代码获取错误主体:

if (!response.isSuccessful()) {
InputStream i = response.errorBody().byteStream();
BufferedReader r = new BufferedReader(new InputStreamReader(i));
StringBuilder errorResult = new StringBuilder();
String line;
try {
while ((line = r.readLine()) != null) {
errorResult.append(line).append('\n');
}
} catch (IOException e) {
e.printStackTrace();
}
}

我解决了这个问题:

 if(!response.isSuccessful()){
Gson gson = new Gson();
MyErrorMessage message=gson.fromJson(response.errorBody().charStream(),MyErrorMessage.class);
if(message.getCode()==ErrorCode.DUPLICATE_EMAIL_ID_CODE){
//DO Error Code specific handling
}else{
//DO GENERAL Error Code Specific handling
}
}

MyErrorMessage类:

  public class MyErrorMessage {
private int code;
private String message;


public int getCode() {
return code;
}


public void setCode(int code) {
this.code = code;
}


public String getMessage() {
return message;
}


public void setMessage(String message) {
this.message = message;
}
}
try{
ResponseBody response = ((HttpException) t).response().errorBody();
JSONObject json = new JSONObject( new String(response.bytes()) );
errMsg = json.getString("message");
}catch(JSONException e){
return t.getMessage();
}
catch(IOException e){
return t.getMessage();
}
 @Override
public void onResponse(Call<Void> call, retrofit2.Response<Void> response) {
if (response.isSuccessful()) {


//Do something if response is ok
} else {


JsonParser parser = new JsonParser();
JsonElement mJson = null;
try {
mJson = parser.parse(response.errorBody().string());
Gson gson = new Gson();
MyError errorResponse = gson.fromJson(mJson, MyError.class);
} catch (IOException ex) {
ex.printStackTrace();
}


}

在科特林:

val call = APIClient.getInstance().signIn(AuthRequestWrapper(AuthRequest("1234567890z", "12341234", "nonce")))
call.enqueue(object : Callback<AuthResponse> {
override fun onResponse(call: Call<AuthResponse>, response: Response<AuthResponse>) {
if (response.isSuccessful) {


} else {
val a = object : Annotation{}
val errorConverter = RentalGeekClient.getRetrofitInstance().responseBodyConverter<AuthFailureResponse>(AuthFailureResponse::class.java, arrayOf(a))
val authFailureResponse = errorConverter.convert(response.errorBody())
}
}


override fun onFailure(call: Call<AuthResponse>, t: Throwable) {
}
})

这样,如果您只是注入从Retrofit创建的服务,则不需要Retrofit实例。

public class ErrorUtils {


public static APIError parseError(Context context, Response<?> response) {


APIError error = new APIError();


try {
Gson gson = new Gson();
error = gson.fromJson(response.errorBody().charStream(), APIError.class);
} catch (Exception e) {
Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show();
}


if (TextUtils.isEmpty(error.getErrorMessage())) {
error.setError(response.raw().message());
}
return error;
}
}

像这样使用:

if (response.isSuccessful()) {


...


} else {


String msg = ErrorUtils.parseError(fragment.getActivity(), response).getError(); // would be from your error class
Snackbar.make(someview, msg, Snackbar.LENGTH_LONG).show();
}
}

错误响应是您的自定义响应对象

科特林

val gson = Gson()
val type = object : TypeToken<ErrorResponse>() {}.type
var errorResponse: ErrorResponse? = gson.fromJson(response.errorBody()!!.charStream(), type)

爪哇

Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse errorResponse = gson.fromJson(response.errorBody.charStream(),type);

ErrorBody值应在Retrofit中设置ApiError对象。因此,您可以使用下面的代码结构。

public class APIErrorUtils {


public static APIError parseError(Response<?> response) {
Converter<ResponseBody, APIError> converter = API.getClient().responseBodyConverter(APIError.class, new Annotation[0]);


APIError error;


try {
error = converter.convert(response.errorBody());
Log.d("SERVICELOG", "****************************************************");
Log.d("SERVICELOG", "***** SERVICE LOG");
Log.d("SERVICELOG", "***** TIMESTAMP: " + String.valueOf(error.getTimestamp()));
Log.d("SERVICELOG", "***** STATUS: " + String.valueOf(error.getStatus()));
Log.d("SERVICELOG", "***** ERROR: " + error.getError());
Log.d("SERVICELOG", "***** MESSAGE: " + error.getMessage());
Log.d("SERVICELOG", "***** PATH: " + error.getPath());
Log.d("SERVICELOG", "****************************************************");
} catch (IOException e) {
return new APIError();
}


return error;
}
}


APIError error = APIErrorUtils.parseError(response);
if (error.getStatus() == 400) {
....
}

我也面临着同样的问题。我用改装解决了这个问题。让我看看这个..

如果您的错误JSON结构类似于

{
"error": {
"status": "The email field is required."
}
}




My ErrorRespnce.java


public class ErrorResponse {


@SerializedName("error")
@Expose
private ErrorStatus error;


public ErrorStatus getError() {
return error;
}


public void setError(ErrorStatus error) {
this.error = error;
}
}

这是我的错误状态类

public class ErrorStatus {


@SerializedName("status")
@Expose
private String status;


public String getStatus() {
return status;
}


public void setStatus(String status) {
this.status = status;
}
}

现在我们需要一个可以处理我们的JSON的类。

  public class ErrorUtils {


public static ErrorResponse parseError (Response<?> response){
Converter<ResponseBody , ErrorResponse> converter =          ApiClient.getClient().responseBodyConverter(ErrorResponse.class , new Annotation[0]);
ErrorResponse errorResponse;
try{
errorResponse = converter.convert(response.errorBody());
}catch (IOException e){
return new ErrorResponse();
}
return errorResponse;
}
}

现在我们可以在Retrofit API调用中检查我们的响应

private void registrationRequest(String name , String email , String password , String c_password){




final Call<RegistrationResponce> registrationResponceCall = apiInterface.getRegistration(name , email , password , c_password);
registrationResponceCall.enqueue(new Callback<RegistrationResponce>() {
@Override
public void onResponse(Call<RegistrationResponce> call, Response<RegistrationResponce> response) {






if (response.code() == 200){




}else if (response.code() == 401){




ErrorResponse errorResponse = ErrorUtils.parseError(response);
Toast.makeText(MainActivity.this, ""+errorResponse.getError().getStatus(), Toast.LENGTH_SHORT).show();
}
}


@Override
public void onFailure(Call<RegistrationResponce> call, Throwable t) {


}
});
}

好了,现在你可以展示你的祝酒词了。

如果您使用Kotlin,另一个解决方案可能是为响应类创建扩展函数:

inline fun <reified T>Response<*>.parseErrJsonResponse(): T?
{
val moshi = MyCustomMoshiBuilder().build()
val parser = moshi.adapter(T::class.java)
val response = errorBody()?.string()
if(response != null)
try {
return parser.fromJson(response)
} catch(e: JsonDataException) {
e.printStackTrace()
}
return null
}

用法

val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
// handle your error logic here
// ...
}
if(!response.isSuccessful()) {
StringBuilder error = new StringBuilder();
try {
BufferedReader bufferedReader = null;
if (response.errorBody() != null) {
bufferedReader = new BufferedReader(new InputStreamReader(
response.errorBody().byteStream()));


String eLine = null;
while ((eLine = bufferedReader.readLine()) != null) {
error.append(eLine);
}
bufferedReader.close();
}


} catch (Exception e) {
error.append(e.getMessage());
}


Log.e("Error", error.toString());
}

测试和工作

 public BaseModel parse(Response<BaseModel> response , Retrofit retrofit){
BaseModel error = null;
Converter<ResponseBody, BaseModel> errorConverter =
retrofit.responseBodyConverter(BaseModel.class, new Annotation[0]);
try {
if (response.errorBody() != null) {
error = errorConverter.convert(response.errorBody());
}
} catch (IOException e) {
e.printStackTrace();
}
return error;
}
val error = JSONObject(callApi.errorBody()?.string() as String)
CustomResult.OnError(CustomNotFoundError(userMessage = error["userMessage"] as String))


open class CustomError (
val traceId: String? = null,
val errorCode: String? = null,
val systemMessage: String? = null,
val userMessage: String? = null,
val cause: Throwable? = null
)


open class ErrorThrowable(
private val traceId: String? = null,
private val errorCode: String? = null,
private val systemMessage: String? = null,
private val userMessage: String? = null,
override val cause: Throwable? = null
) : Throwable(userMessage, cause) {
fun toError(): CustomError = CustomError(traceId, errorCode, systemMessage, userMessage, cause)
}




class NetworkError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage?: "Usted no tiene conexión a internet, active los datos", cause)


class HttpError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage, cause)


class UnknownError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage?: "Unknown error", cause)


class CustomNotFoundError(traceId: String? = null, errorCode: String? = null, systemMessage: String? = null, userMessage: String? = null, cause: Throwable? = null):
CustomError(traceId, errorCode, systemMessage, userMessage?: "Data not found", cause)`

创建错误响应模型&;用户gson将响应转换为它。这会工作得很好。

ApiError.Java

public class APIError {
private String message;


public String getMessage() {
return message;
}
}

MainActivity.Java(请求响应内)

if (response.isSuccessful()) {
// Do your success stuff...


} else {
APIError message = new Gson().fromJson(response.errorBody().charStream(), APIError.class);
Toast.makeText(MainActivity.this, "" + message.getMessage(), Toast.LENGTH_SHORT).show();
}

下面是一个使用Kotlin扩展的优雅解决方案:

data class ApiError(val code: Int, val message: String?) {
companion object {
val EMPTY_API_ERROR = ApiError(-1, null)
}
}


fun Throwable.getApiError(): ApiError? {
if (this is HttpException) {
try {
val errorJsonString = this.response()?.errorBody()?.string()
return Gson().fromJson(errorJsonString, ApiError::class.java)
} catch (exception: Exception) {
// Ignore
}
}
return EMPTY_API_ERROR
}

和用法:

showError(retrofitThrowable.getApiError()?.message)

这其实很简单。

科特林:

val jsonObj = JSONObject(response.errorBody()!!.charStream().readText())
responseInterface.onFailure(jsonObj.getString("msg"))

Java:

    if(response.errorBody()!=null){
JSONObject jsonObj = new JSONObject(TextStreamsKt.readText(response.errorBody().charStream()));
responseInterface.onFailure(jsonObj.getString("msg"));
}else{
responseInterface.onFailure("you might want to return a generic error message.");
}
改装

测试:2.5.0。 从CharStream中读取文本,它将为您提供一个字符串,然后解析为JSONObject.

再见。

Kotlin Android中的错误主体处理

catch (cause: Throwable) {
when (cause) {
is HttpException -> {
try {
val YourErrorResponseClassObj = Gson().fromJson(cause.response()?.errorBody()?.charStream(), YourErrorResponseClass::class.java)
} catch (e: Exception) {
                        

}
}
else -> {
//Other errors like Network ...
}
}
}

已经有很多有效的答案了。当您需要多次使用相同的改造响应时,这只是用例的一个补充。以下两者都不能用作您只能读取响应正文一次,因为它将在之后关闭,并且当您尝试从同一响应对象读取时,每次下次都将获得null

response()?.errorBody()?.charStream()?.readText()
response()?.errorBody()?.string()

相反,您可以获得响应字符串的只读副本(而响应本身可以被传递并最终在以后使用):

response()?.errorBody()?.source()?.buffer?.snapshot()?.utf8()

非常简单。这救了我的命

public static void displayApiResponseErrorBody(Response<?> response)
{
InputStream i = response.errorBody().byteStream();
BufferedReader r = new BufferedReader(new InputStreamReader(i));
StringBuilder errorResult = new StringBuilder();
String line;
try {
while ((line = r.readLine()) != null)
{
errorResult.append(line).append('\n');
}
Log.d("API_RESPONSE_ERROR_BODY",String.valueOf(errorResult));
System.out.println(errorResult);
} catch (IOException e) {
e.printStackTrace();
}
}

如果错误响应是字符串,则可以使用以下Kotlin代码对其进行反序列化:

val errorString = response.errorBody()?.byteStream()?.bufferedReader().use { it?.readText() }  // defaults to UTF-8

JSON响应

{
"success": false,
"status_code": 32,
"status_message": "Email not verified: Your email address has not been verified."
}

错误类

data class ResponseError(
@SerializedName("status_code")
val statusCode: Int,
@SerializedName("status_message")
val statusMessage: String,
@SerializedName("success")
val success: Boolean
)

获取错误消息

fun <T : Any> getResultOrError(response: Response<T>): T? {
if (response.isSuccessful) {
return response.body()
} else {
try {
val responseError = Gson().fromJson(
response.errorBody()?.string(),
ResponseError::class.java
)
throw Throwable(responseError.statusMessage)
} catch (e: Exception) {
throw Throwable("Unknown error")
}
}
}

在改装错误响应的情况下,您可以使用Error.getResponse()获取主体,下面是示例。

        @Override
public void failure(RetrofitError error){
if(error.getResponse().getStatus()==201){
LogUtil.INSTANCE.debug("Success : " + error.toString());
callback.success(error.getResponse().getBody);
}else{
LogUtil.INSTANCE.debug("failure: " + error.toString());
callback.failure(error);
}
}

在Kotlin中,我解决了这个问题,创建了一个自定义的responseBody通用扩展函数,该函数将响应主体转换为JSONObject.然后,您可以使用GSON通过自定义错误数据类自定义错误响应主体。

inline fun <reified T> ResponseBody.getErrorObject(): T {
val gson = Gson()
val jsonObject = JSONObject(charStream().readText())
return gson.fromJson(jsonObject.toString(), T::class.java)
}

然后,您可以将错误响应定制到您的定制类。为此,我举了一个例子。

data class LoginError(
val error: Error,
val message: String,
val success: Boolean
)


data class Error(
val error: String,
val status: Int
)

然后这样使用扩展函数

val error = state.errorBody.getErrorObject<LoginError>()

state.errorBody是我对ResponseBody类型的Retrofit的错误响应

我这样做了,效果很好。

val errorBody = (response?.errorBody() as ResponseBody).string()

对于将Kotlin与Moshi和协程一起使用的人,我是这样做的:

错误数据类

@JsonClass(generateAdapter = true)
data class ApiResponseNoData(
val exito: Int,
val error: String?
)

延展

fun ResponseBody.getApiError(): String? {
return try {
Moshi
.Builder()
.build()
.adapter(ApiResponseNoData::class.java)
.fromJson(string())
?.error
}catch(e: Exception) { null }
}

视图模型

fun test() {
viewModelScope.launch(Dispatchers.IO) {
val response = repository.test()
withContext(Dispatchers.Main) {
if(response.isSuccessful) {
...
}else{
val errorMsg = response.errorBody()?.getApiError() ?: "Unexpected error occurred"
...
]
}
}
}
val reader = BufferedReader(response.errorBody()?.source().inputStream().reader())
                

val content = StringBuilder()


reader.use { readerBuffer ->
var line = readerBuffer.readLine()
while (line != null) {
content.append(line)
line = readerBuffer.readLine()
}
}


Gson().fromJson(content.toString(), ResponseData::class.java)