Retrofit 2.0, jak uzyskać deserializowaną odpowiedź na błąd. Body

128

Używam Retrofit 2.0.0-beta1 .

W testach mam alternatywny scenariusz i spodziewam się błędu HTTP 400

Chciałbym mieć retrofit.Response<MyError> response aleresponse.body() == null

MyError nie jest zdeserializowany - widzę to tylko tutaj

response.errorBody().string()

ale nie daje mi MyError jako obiektu

Piotr Boho
źródło
czy dobrą praktyką jest deserializacja odpowiedzi błędu? ponieważ odpowiedź może być błędem serwera WWW, który jest html.
Hossein Shahdoost
1
thx @ahmadalibaloch, ten link jest naprawdę bardzo pomocny.
Ravi Vaniya

Odpowiedzi:

138

Obecnie korzystam z bardzo łatwej implementacji, która nie wymaga stosowania konwerterów ani specjalnych klas. Kod, którego używam, jest następujący:

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();
        }
    }
}
Saif Bechan
źródło
6
Twoje rozwiązanie nie wyświetla treści odpowiedzi o błędzie.
CoolMind
1
Sprawdź moją edycję, nie wiem, dlaczego uczyniłem to tak niejasnym.
Saif Bechan
4
Na koniec prosta odpowiedź na pytanie dotyczące Androida, które działa (większość odpowiedzi na Androida jest komicznie skomplikowana).
Doug Voss
To zdecydowanie nie jest odpowiedzią na pytanie. Po prostu zwraca komunikat o błędzie, a nie obiekt Error Enum. Postępuj zgodnie z tą odpowiedzią: stackoverflow.com/a/21103420/2914140
Tobliug
W odpowiedzi szuka mapowania na „komunikat”, ale moja odpowiedź o błędzie nie zawierała tego. Miał mapowanie na „błąd”. Więc wszyscy czytający, zależy to od otrzymanej odpowiedzi!
mco
43

ErrorResponse to niestandardowy obiekt odpowiedzi

Kotlin

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

Jawa

Gson gson = new Gson();
Type type = new TypeToken<ErrorResponse>() {}.getType();
ErrorResponse errorResponse = gson.fromJson(response.errorBody.charStream(),type);
Shahab Rauf
źródło
3
nie powinieneś wymuszać rozpakowywania opcji:gson.fromJson(response.errorBody()?.charStream(), type)
hopeman
37

Rozwiązałem to przez:

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

Klasa 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;
     }
   }
Pooja Gupta
źródło
2
java.lang.IllegalStateException: Oczekiwano BEGIN_OBJECT, ale był STRING w linii 1, kolumna 2 ścieżka $
Ronel Gonzales
Użyj .addConverterFactory(ScalarsConverterFactory.create())@RonelGonzales
Pratik Butani
30

W Retrofit 2.0 beta2 tak otrzymuję odpowiedzi na błędy:

  1. Synchroniczny

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

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

Aktualizacja dla Retrofit 2 beta3:

  1. Synchroniczne - niezmienione
  2. Asynchroniczny - parametr Retrofit został usunięty z 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
        }
    });
JFreeman
źródło
4
co masz w BasicResponse?
Jemshit Iskenderov
2
Tylko podstawowa klasa z adnotacjami Jacksona, która zawiera komunikat i kod błędu. W każdym razie możesz mieć tam dowolną klasę z adnotacjami, która odpowiada typowi odpowiedzi serwera. Spróbuj użyć jsonschema2pojo, aby wygenerować taki, który odpowiada Twoim potrzebom.
JFreeman,
Dla innych możesz użyć tego zamiast: Converter <ResponseBody, <Message> errorConverter = retrofit.responseBodyConverter (Message.class, new Annotation [0]);
Kim Montano
@JFreeman, co jeśli chcę deserializować List<BasicResponse>?
azizbekian
czy możesz dla mnie zobaczyć klasę MyApplication
dungtv
10

Na https://stackoverflow.com/a/21103420/2914140 i https://futurestud.io/tutorials/retrofit-2-simple-error-handling ten wariant jest pokazany dla 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();
            }
        }
    }
CoolMind
źródło
9
 @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();
                }

            }
Vins
źródło
9

Utwórz model odpowiedzi błędu i użytkownika Gson, aby przekonwertować na niego odpowiedź. To po prostu zadziała.

APIError.java

public class APIError {
    private String message;

    public String getMessage() {
        return message;
    }
}

MainActivity.java (wewnątrz żądania 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();
}
Sreekant Shenoy
źródło
7

Zrobiłem to w ten sposób dla wywołań asynchronicznych przy użyciu 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();
        }
    }
}
shantanu
źródło
Jaka będzie klasa MyError?
Dhrupal
Myślałem, że onResponse powinien zawierać parametr Call i parametr Response. Jak to się dzieje, że twój ma parametr Retrofit?
Marty Miller
@MartyMiller zostało to zrobione dla następującej wersji retrofitu Retrofit 2.0-beta2
shantanu
7

Jeśli używasz Kotlina, innym rozwiązaniem może być po prostu utworzenie funkcji rozszerzenia dla klasy 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
}

Stosowanie

val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
   // handle your error logic here
   // ...
}
Arsenius
źródło
Wreszcie ktoś użył mocy Kotlin, aby uczynić kod łatwym do odczytania!
Vince,
6

W rzeczywistości jest to bardzo proste.

Kotlin:

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

Jawa:

JSONObject jsonObj = new JSONObject(response.errorBody().charStream().readText());
responseInterface.onFailure(jsonObj.getString("msg"));

Testowane przy modernizacji: 2.5.0. Przeczytaj tekst z charStream, który da ci String, a następnie przeanalizuj do JSONObject.

Adios.

Whales_Corps
źródło
nie ma readText()rozszerzenia w java, użyj, TextStreamsKt.readText(response.errorBody().charStream())jeśli nadal jesteś w java
mochadwi
4

Miałem ten sam problem. Rozwiązałem to za pomocą modernizacji. Pokażę to ...

Jeśli twoja błędna struktura JSON jest podobna do

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

A to moja klasa statusu błędu

public class ErrorStatus {

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

  public String getStatus() {
      return status;
  }

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

Teraz potrzebujemy klasy, która poradzi sobie z naszym jsonem.

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

Teraz możemy sprawdzić naszą odpowiedź w wywołaniu api modernizacji

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

        }
    });
}

To wszystko, teraz możesz pokazać swój Toast

pavel
źródło
4

Oto eleganckie rozwiązanie wykorzystujące Kotlinrozszerzenia:

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
}

i zastosowanie:

showError(retrofitThrowable.getApiError()?.message)

Antonis Radz
źródło
3

W ten sposób nie potrzebujesz instancji Retrofit, jeśli wstrzykujesz tylko usługę utworzoną za pomocą 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;
  }
}

Użyj tego w ten sposób:

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();
    }
  }
Codeversed
źródło
2

Wydaje się, że jest to problem, gdy używasz OkHttp wraz z Retrofit, więc możesz usunąć OkHttp lub użyć poniższego kodu, aby uzyskać treść błędu:

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(); 
}
}
KRUPEN GHETIYA
źródło
2

Przeczytaj errorBody do String i ręcznie przeanalizuj json.

 if(!response.isSuccessful()) {
   
    String error = "";
    try {
        BufferedReader ereader = new BufferedReader(new InputStreamReader(
                response.errorBody().byteStream()));
        String eline = null;
        while ((eline = ereader.readLine()) != null) {
            error += eline + "";
        }
        ereader.close();
    } catch (Exception e) {
        error += e.getMessage();
    }
    Log.e("Error",error);
    
    try {
        JSONObject reader = new JSONObject(error);
        String message = reader.getString("message");
    
        Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
    
    } catch (JSONException e) {
        e.printStackTrace();
    }
 }
Riyas PK
źródło
0

rozwiązał to przez:

Converter<MyError> converter = 
    (Converter<MyError>)JacksonConverterFactory.create().get(MyError.class);
MyError myError =  converter.fromBody(response.errorBody());
Piotr Boho
źródło
Jak mogę dokonać konwersji przez GsonConverterFactory? Dowolny pomysł ?
Shan Xeeshi
Znalazłem drogę. Po prostu zmieniam JacksonConverterFactory na GsonConverterFactoryTo konwertuje json na mój niestandardowy obiekt, ale daje ostrzeżenie Unchecked Cast retrofit.
Konwerter
3
Jakie klasy MyError mają?
Dhrupal
0
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();
            }
Mike6679
źródło
0

W Kotlinie:

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) {
    }
})
Adam Johns
źródło
0

Wartości errorBody powinny ustawiać obiekt APIError w Retrofit. Możesz więc użyć poniższej struktury kodu.

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) {
    ....
}
Egemen Mede
źródło
0

Przetestowane i działa

 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;
        }
MJ
źródło
-1
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)`
Gary Loyola
źródło