簡體   English   中英

Retrofit 2.0 如何獲得反序列化錯誤response.body

[英]Retrofit 2.0 how to get deserialised error response.body

我正在使用Retrofit 2.0.0-beta1

在測試中,我有一個替代方案,預計會出現錯誤 HTTP 400

我想要retrofit.Response<MyError> responseresponse.body() == null

MyError 沒有反序列化 - 我只在這里看到

response.errorBody().string()

但它沒有給我 MyError 作為 object

我目前使用一個非常簡單的實現,不需要使用轉換器或特殊類。 我使用的代碼如下:

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();
        }
    }
}

ErrorResponse是您的自定義響應對象

科特林

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);

我通過以下方式解決了它:

 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;
     }
   }

在 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. 異步

    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 } });

Retrofit 2 beta3 的更新:

  1. 同步 - 未更改
  2. 異步 - 從 onResponse 中刪除了 Retrofit 參數

    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 } });

它實際上非常簡單。

科特林:

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

爪哇:

    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。

阿迪歐斯。

創建錯誤響應和用戶 Gson 的模型以將響應轉換為它。 這將正常工作。

錯誤代碼.java

public class APIError {
    private String message;

    public String getMessage() {
        return message;
    }
}

MainActivity.java (內部請求 onResponse)

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();
}
 @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();
                }

            }

https://stackoverflow.com/a/21103420/2914140https://futurestud.io/tutorials/retrofit-2-simple-error-handling 中,此變體顯示為 Retrofit 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();
            }
        }
    }

如果您使用 Kotlin,另一種解決方案可能是為 Response 類創建擴展函數:

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
   // ...
}

對於使用 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();
        }
    }
}

這是使用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)

我面臨同樣的問題。 我通過改造解決了它。 讓我展示這個...

如果您的錯誤 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;
}
}

現在我們可以在改造 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) {

        }
    });
}

就是這樣,現在你可以展示你的 Toast

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());
}

這樣,如果您只注入從 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();
    }
  }

當您將 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(); 
}
}

一些通用的解決方案怎么樣,我覺得這樣的事情可能有用:

abstract class TestCallback<RESPONSE, ERROR extends Throwable> implements Callback<RESPONSE> {

    Class<ERROR> errorClass;
    Retrofit retrofit;

    TestCallback(Retrofit retrofit, Class<ERROR> errorClass) {
        this.retrofit = retrofit;
        this.errorClass = errorClass;
    }

    abstract void onSuccess(Call<RESPONSE> call, RESPONSE response);

    @Override
    public void onResponse(Call<RESPONSE> call, Response<RESPONSE> response) {
        if (response.isSuccessful()) {
            onSuccess(call, response.body());
            return;
        }

        if (response.errorBody() != null) {
            Converter<ResponseBody, ERROR> converter = retrofit.responseBodyConverter(errorClass, new Annotation[0]);
            ERROR error;
            try {
                error = converter.convert(response.errorBody());
                onFailure(call, error);
            } catch (IOException e) {
                // Conversion error. Add some meaningful message or return a custom error.                
                onFailure(call, new Throwable());
            }
        } else {
            // Unknown HTTP error (errorBody == null). Add some meaningful message or return a custom error.                
            onFailure(call, new Throwable());
        }
    }
}

然后我們可以通過以下方式使用此自定義Callback<>

Call<User> call = retrofit.create(UsersApi.class).signUp(email, password);
call.enqueue(new TestCallback<User, TestError>(retrofit, TestError.class) {
        @Override
        public void onFailure(Call<User> call, Throwable t) {
            if (t instanceof TestError) {

            } else {

            }
        }

        @Override
        void onSuccess(Call<User> call, User response) {
            // No need to check for isSuccessful() + no need to 
            // duplicate the same code for all of your handlers.
        }
    });

對於將 Kotlin 與 Moshi 和協程一起使用的人,這就是我所做的:

錯誤數據 class

@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"
                ...
            ]
        }
    }
}

通過以下方式解決:

Converter<MyError> converter = 
    (Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError =  converter.fromBody(response.errorBody());
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();
            }

在科特林:

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) {
    }
})

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) {
    ....
}

測試和工作

 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)`

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 ...
                }
            }
        }

已經有很多有效的答案。 這只是用例的補充,當您需要多次使用相同的 Retrofit 響應時。 以下都不能使用,因為您只能讀取響應主體一次,因為它會在之后關閉,並且下次嘗試從同一個響應對象讀取時每次都會得到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")
        }
    }
}

如果出現 retrofit 錯誤響應,您可以使用 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 通用擴展 function function 將響應正文轉換為 JSONObject。 那么您可以使用 gson 使用您的自定義錯誤數據 Class 自定義錯誤響應正文。

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

然后,您可以將錯誤響應客戶化到您的自定義 class。 為此,我正在使用一個示例

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

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

然后以這種方式使用擴展名 function

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

state.errorBody是我對 ResponseBody 類型的 retrofit 的錯誤響應

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)

我這樣做了,它就像一個魅力

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM