Nachrüsten 2.0, wie man deserialisierte Fehlerantwort erhält


127

Ich verwende Retrofit 2.0.0-beta1 .

In Tests habe ich ein alternatives Szenario und erwarte Fehler HTTP 400

Ich möchte haben retrofit.Response<MyError> response aberresponse.body() == null

MyError ist nicht deserialisiert - ich sehe es nur hier

response.errorBody().string()

aber es gibt mir nicht MyError als Objekt



Ist es eine gute Praxis, die Fehlerantwort zu deserialisieren? da die Antwort möglicherweise ein Webserver-Fehler ist, der HTML ist.
Hossein Shahdoost

1
thx @ahmadalibaloch, dieser Link ist wirklich sehr, sehr hilfreich.
Ravi Vaniya

Antworten:


137

Ich verwende derzeit eine sehr einfache Implementierung, für die keine Konverter oder speziellen Klassen erforderlich sind. Der Code, den ich verwende, ist der folgende:

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

6
Ihre Lösung zeigt keinen Inhalt der Fehlerantwort an.
CoolMind

1
Überprüfen Sie meine Bearbeitung, keine Ahnung, warum ich es überhaupt so unklar gemacht habe.
Saif Bechan

4
Schließlich eine einfache Antwort auf eine Android-Frage, die funktioniert (die meisten Android-Antworten sind komisch komplex).
Doug Voss

Dies beantwortet die Frage definitiv nicht. Es wird einfach die Fehlermeldung und nicht das Fehleraufzählungsobjekt zurückgegeben. Bitte folgen Sie dieser Antwort: stackoverflow.com/a/21103420/2914140
Tobliug

In der Antwort wird nach einer Zuordnung zu "Nachricht" gesucht, aber meine Fehlerantwort hatte das nicht. Es hatte eine Zuordnung zu "Fehler". Also jeder, der liest, es hängt von der Antwort ab, die Sie erhalten!
mco

43

ErrorResponse ist Ihr benutzerdefiniertes Antwortobjekt

Kotlin

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

Java

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

3
Sie sollten nicht gezwungen sein, gson.fromJson(response.errorBody()?.charStream(), type)
Optionen auszupacken

37

Ich habe es gelöst durch:

 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-Klasse:

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

2
java.lang.IllegalStateException: Erwartet BEGIN_OBJECT, war aber STRING in Zeile 1 Spalte 2 Pfad $
Ronel Gonzales

Verwenden Sie .addConverterFactory(ScalarsConverterFactory.create())@RonelGonzales
Pratik Butani

30

In Retrofit 2.0 Beta2 erhalte ich auf diese Weise Fehlerantworten:

  1. Synchron

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

    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 für Retrofit 2 Beta3:

  1. Synchron - nicht geändert
  2. Asynchron - Der Retrofit-Parameter wurde aus onResponse entfernt

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

4
Was haben Sie in BasicResponse?
Jemshit Iskenderov

2
Nur eine mit Jackson kommentierte Basisklasse, die Nachrichten- und Fehlercode enthält. In jedem Fall können Sie dort eine mit Anmerkungen versehene Klasse haben, die Ihrem Serverantworttyp entspricht. Versuchen Sie, mit jsonschema2pojo eine zu generieren, die Ihren Anforderungen entspricht.
JFreeman

Für andere können Sie stattdessen Folgendes verwenden: Converter <ResponseBody, <Message> errorConverter = retrofit.responseBodyConverter (Message.class, new Annotation [0]);
Kim Montano

@JFreeman, was ist, wenn ich deserialisieren möchte List<BasicResponse>?
Azizbekian

können Sie für mich Klasse MyApplication
dungtv

10

In https://stackoverflow.com/a/21103420/2914140 und https://futurestud.io/tutorials/retrofit-2-simple-error-handling wird diese Variante für Retrofit 2.1.0 angezeigt.

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

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

            }

9

Erstellen Sie ein Modell der Fehlerantwort und des Benutzers Gson, um die Antwort darauf zu konvertieren. Dies wird gut funktionieren.

APIError.java

public class APIError {
    private String message;

    public String getMessage() {
        return message;
    }
}

MainActivity.java (innerhalb der Anfrage 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();
}

7

Ich habe es so gemacht für asynchrone Anrufe mit 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();
        }
    }
}

Was wird die MyError-Klasse sein?
Dhrupal

Ich dachte, onResponse sollte einen Call-Parameter und einen Response-Parameter enthalten. Wie kommt es, dass Ihr Parameter einen Retrofit-Parameter hat?
Marty Miller

@MartyMiller Dies wurde für die folgende Version von Retrofit 2.0-Beta2 nachgerüstet
Shantanu

7

Wenn Sie Kotlin verwenden, könnte eine andere Lösung darin bestehen, eine Erweiterungsfunktion für die Antwortklasse zu erstellen:

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
}

Verwendung

val myError = response.parseErrJsonResponse<MyErrorResponse>()
if(myError != null) {
   // handle your error logic here
   // ...
}

Endlich hat jemand Kotlin Power benutzt, um Code leicht lesbar zu machen!
Vince

5

Es ist eigentlich sehr einfach.

Kotlin:

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

Java:

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

Nachrüstung getestet: 2.5.0. Lesen Sie den Text aus dem charStream, der Ihnen einen String gibt, und analysieren Sie ihn dann in JSONObject.

Adios.


Es gibt keine readText()Erweiterung für Java. Verwenden TextStreamsKt.readText(response.errorBody().charStream())Sie diese
Option

4

Ich stand vor dem gleichen Problem. Ich habe es mit Nachrüstung gelöst. Lassen Sie mich das zeigen ...

Wenn Ihre Fehler-JSON-Struktur wie folgt ist

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

Und das ist meine Fehlerstatusklasse

public class ErrorStatus {

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

  public String getStatus() {
      return status;
  }

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

Jetzt brauchen wir eine Klasse, die mit unserem json umgehen kann.

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

Jetzt können wir unsere Antwort im Nachrüst-API-Aufruf überprüfen

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

        }
    });
}

Jetzt können Sie Ihren Toast zeigen


3

Auf diese Weise benötigen Sie keine Retrofit-Instanz, wenn Sie nur einen aus Retrofit erstellten Service einfügen.

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

Verwenden Sie es so:

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

3

Hier ist eine elegante Lösung mit KotlinErweiterungen:

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
}

und Verwendung:

showError(retrofitThrowable.getApiError()?.message)


2

Dies scheint das Problem zu sein, wenn Sie OkHttp zusammen mit Retrofit verwenden. Sie können also entweder OkHttp entfernen oder den folgenden Code verwenden, um den Fehlerkörper zu erhalten:

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

2

Lesen Sie errorBody in einen String und analysieren Sie json manuell.

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

0

löste es durch:

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

Wie kann ich über GsonConverterFactory konvertieren? Irgendeine Idee ?
Shan Xeeshi

Ich habe einen Weg gefunden. Ich wechsle nur JacksonConverterFactory um GsonConverterFactoryes json in mein benutzerdefiniertes Objekt konvertiert , aber es gibt Warnung Ungeprüfte Guss retrofit.Converter <capture <>>?
Shan Xeeshi

3
Welche MyError-Klasse hat?
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();
            }

0

In Kotlin:

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

0

errorBody-Werte sollten das APIError-Objekt in Retrofit festlegen. Damit können Sie die folgende Codestruktur verwenden.

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

0

Getestet und funktioniert

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

-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)`
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.