I am using the Retrofit library to do REST calls to a service I am using.

If I make an API call to my service and have a failure, the service returns a bit of JSON along with the standard HTTP error stuff. Using the RetrofitError object included in the failure callback I am able to find the HTTP status code and several other things, however I am not able to retrieve the JSON that the service sends back.

For example, let's say I make a call to the API where I am trying to create a user. If the username already exists the service will return a 400 error code along with some JSON like this:

{"error":"Username already in use"}

Because a simple 400 error code isn't specific enough I really need access to the JSON that is returned.

Does anyone know how I can get at this JSON data? I have tried looking at every field in the RetrofitError object and can't find it anywhere. Is there something additional I need to be doing?

有帮助吗?

解决方案

You can use the getBodyAs method of the RetrofitError object. It converts the response to a Java object similarly to other Retrofit conversions. First define a class that describes your JSON error response:

class RestError {
    @SerializedName("code")
    public int code;
    @SerializedName("error")
    public String errorDetails;
}

Then use the previously mentioned method to get the object that describes the error in more detail.

catch(RetrofitError error) {
    if (error.getResponse() != null) {
        RestError body = (RestError) error.getBodyAs(RestError.class);
        log(body.errorDetails);
        switch (body.code) {
            case 101:
                ...
            case 102:
                ...
        }
    }
}

Retrofit 2.0 changed the way error responses are converter. You will need to get the right converter with the responseBodyConverter method and use it to convert the error body of the response. Barring exception handling this would be:

Converter<ResponseBody, RestError> converter 
    = retrofit.responseBodyConverter(RestError.class, new Annotation[0]);
RestError errorResponse = converter.convert(response.errorBody());

其他提示

Try this code

@Override
public void failure(RetrofitError error) {
    String json =  new String(((TypedByteArray)error.getResponse().getBody()).getBytes());
    Log.v("failure", json.toString());
}

with Retrofit 2.0

@Override
public void onFailure(Call<Example> call, Throwable t) {
    String message = t.getMessage();
    Log.d("failure", message);
}

@LukaCiko answer not working now for me in retrofit 1.6.1. Here like I'm doing it now:

    @Override
    public void failure(RetrofitError retrofitError) {
        String json =  new String(((TypedByteArray)retrofitError.getResponse().getBody()).getBytes());
        //own logic for example
        ExampleHandlerError error = new Gson().fromJson(json, ExampleHandlerError.class);
    }

Two-Liner with getBodyAs to get a JSON Object.

JsonObject responseAsJson = (JsonObject) retrofitError.getBodyAs(JsonElement.class);

String message = responseAsJson.get("error").getAsString(); //=> "Username already in use"

Confirmed working in Retrofit 1.x. Not sure what changes are required for Retrofit 2.x.

Using this you can get the error body

  if (response != null && response.errorBody() != null) {
    JSONObject jsonObject = new JSONObject(response.errorBody().string());
    String error =  jsonObject.getString("error");
  }

A much neater way to do this is to create a class that can do the parsing/conversion for you, one that you can call anytime, anywhere.

public class ApiError {
    public String error = "An error occurred";

    public ApiError(Throwable error) {
        if (error instanceof HttpException) {
            String errorJsonString = null;
            try {
                errorJsonString = ((HttpException) 
                error).response().errorBody().string();
            } catch (IOException e) {
                e.printStackTrace();
            }
            JsonElement parsedString = new 
            JsonParser().parse(errorJsonString);
            this.error = parsedString.getAsJsonObject()
                                     .get("error")
                                     .getAsString();
        } else {
            this.error = error.getMessage() != null ? error.getMessage() : this.error;
        }
    }
}

You can now use this anywhere, like so

ApiError error = new ApiError(error)
error.error

This is all from this blog post (which I wrote).

So after a little more hunting I found the answer.

Here's my failure callback:

@Override public void failure(RetrofitError retrofitError) {
    String serverError = null;
    try {
        serverError = extractServerError(retrofitError.getResponse().getBody().in());
    } catch (Exception e) {
        Log.e("LOG_TAG", "Error converting RetrofitError server JSON", e);
    }

    if (serverError!=null) {
        Log.i(LOG_TAG, serverError);
    }

    Intent intent = new Intent(ACTION_REGISTRATION_ERROR);
    intent.putExtra("ServerError", serverError);
    LocalBroadcastManager.getInstance(FamDooApplication.CONTEXT).sendBroadcastSync(intent);
}

And here's the method that is called to extract the server error:

public static String extractServerError(java.io.InputStream is) {
    String serverError = null;
    String serverErrorDescription = null;
try {

    String s = convertStreamToString(is);

    JSONObject messageObject = new JSONObject(s);
    serverError = messageObject.optString("error");
    serverErrorDescription = messageObject.optString("error_description");
    if (serverErrorDescription!=null && !serverErrorDescription.equals("")) {
        return serverErrorDescription;
    } else {
        return serverError;
    }
    //String serverStack = messageObject.getString("stack");
} catch (Exception e) {
    Log.e("Basemodel", "Error converting RetrofitError server JSON", e);
}

return "";
}

This will extract the error information sent by the service that is encoded in the JSON.

I compile many answer and wrote some code to achieve something nicer :

{
    "errors": {
        "email": [
            "Email not valid.",
            "Unable to find the user toto@toto.fr."
        ]
    }
}

I take all item in 'email' and display them concatained with Guava Joiner:

String json =  new String(((TypedByteArray)error.getResponse()
    .getBody()).getBytes());

Map<String, Object> map = new Gson().fromJson(
    json, new TypeToken<Map<String, Map<String, List<String>>>>() {}.getType());

try {
    List<String> errorsEmail = 
        (List<String>) ((Map)map.get("errors")).get("email");
    Toast.makeText(getApplicationContext(), Joiner.on("\n")
        .join(errorsEmail), Toast.LENGTH_SHORT).show();
} catch(Exception e){
    Log.e(Constants.TAG, e.getMessage());
}

I was having the same. My problem was a long field that was coming from server as an empty String "". Retrofit was giving NumberFormatException because it seems like Gson doesn't convert an empty string to long (I'd suggest to make it 0L or something). So I had to change:

long errorCode;

to

String errorCode;

As it was said before, I had no access to the JSON message when debugging. I finally found the error using RequestMaker page, maybe it helps someone else

http://requestmaker.com/

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top