Retrofit 2.0如何获得反序列化错误response.body

Posted

技术标签:

【中文标题】Retrofit 2.0如何获得反序列化错误response.body【英文标题】:Retrofit 2.0 how to get deserialised error response.body 【发布时间】:2015-12-07 18:54:20 【问题描述】:

我正在使用 Retrofit 2.0.0-beta1

在测试中,我有一个替代方案,预计会出现 HTTP 400 错误

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

MyError 没有反序列化 - 我只在这里看到它

response.errorBody().string()

但它没有给我 MyError 作为对象

【问题讨论】:

简单的futurestud.io/tutorials/retrofit-2-simple-error-handling 反序列化错误响应是一个好习惯吗?因为响应可能是 html 的网络服务器错误。 谢谢@ahmadalibaloch,这个链接真的很有帮助。 【参考方案1】:

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

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() 只会返回一次正确的值。如果再次调用它,它将返回一个空字符串。因此,如果您想重用它,请在第一次调用时将值存储在变量中。

【讨论】:

您的解决方案没有显示错误响应内容。 检查我的编辑,不知道为什么我一开始就这么不清楚。 最后,简单回答一个有效的 android 问题(大多数 android 答案都非常复杂)。 这绝对不是在回答问题。它只是返回错误消息而不是错误枚举对象。请关注此回复:***.com/a/21103420/2914140 在答案中它正在寻找到“消息”的映射,但我的错误响应没有。它有一个映射到“错误”。所以每个人都在阅读,这取决于你得到的回应!【参考方案2】:

ErrorResponse 是您的自定义响应对象

科特林

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

【讨论】:

你不应该强制解包选项:gson.fromJson(response.errorBody()?.charStream(), type)【参考方案3】:

我解决了这个问题:

 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;
     
   

【讨论】:

java.lang.IllegalStateException: 应为 BEGIN_OBJECT 但在第 1 行第 2 列路径 $ 使用.addConverterFactory(ScalarsConverterFactory.create())@RonelGonzales【参考方案4】:

在 Retrofit 2.0 beta2 中,这是我收到错误响应的方式:

    同步

    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
    
    

    异步

    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 更新:

    同步 - 未更改

    异步 ​​- Retrofit 参数已从 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
        
    );
    

【讨论】:

BasicResponse 中有什么? 只是一个包含消息和错误代码的基本 Jackson 注释类。在任何情况下,您都可以在那里拥有与您的服务器响应类型匹配的任何带注释的类。尝试使用jsonschema2pojo 生成一个符合您需求的文件。 对于其他人,您可以使用它来代替:Converter errorConverter = retrofit.responseBodyConverter(Message.class, new Annotation[0]); @JFreeman,如果我想反序列化List&lt;BasicResponse&gt;怎么办? 你能帮我看看 MyApplication 类吗【参考方案5】:

其实很简单。

科特林:

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。

再见。

【讨论】:

java 上没有 readText() 扩展,如果你还在使用 java,请使用 TextStreamsKt.readText(response.errorBody().charStream()) 更新:改造 2.6.2 -> val errorMessage=jsonObj.getJSONArray("errors").getJSONObject(0).getString("message") @Wale,为什么这对没有错误的 body 不起作用? JSONObject(response.body()!!.charStream().readText()) @Oleksandr 我不确定我是否收到您的问题,但我认为您的 response.body 没有 byteStream 函数,如果您使用 scala 转换器或相关或它,它可能以字符串形式出现如果您使用 google gson 转换器,可以简单地作为您传递的对象。所以,你想要做的可能是 this=> JSONObject (response.errorBody()!!.charStream().readText()) @TaslimOseni 我很高兴它也对你有用。【参考方案6】:

创建错误响应和用户 Gson 的模型以将响应转换为它。这样就可以了。

APIError.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();

【讨论】:

这是迄今为止最优雅的方式。感谢您的回答...【参考方案7】:
 @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();
                

            

【讨论】:

【参考方案8】:

在 https://***.com/a/21103420/2914140 和 https://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();
            
        
    

【讨论】:

【参考方案9】:

如果您使用 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
   // ...

【讨论】:

终于有人使用 Kotlin 的力量让代码易于阅读!【参考方案10】:

我使用 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();
        
    

【讨论】:

什么是 MyError 类? 我认为 onResponse 应该包含一个 Call 参数和一个 Response 参数。你的怎么有 Retrofit 参数? @MartyMiller 这是为以下版本的改造改造 2.0-beta2 完成的【参考方案11】:

我遇到了同样的问题。我通过改造解决了它。让我展示一下……

如果你的错误 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

【讨论】:

应用程序崩溃 ErrorResponse$ErrorStatus.getStatus()' 在空对象引用上 它进入 ErrorUtils 的 catch 块,并说 End of input at line 1 column 1 path $【参考方案12】:

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

【讨论】:

喜欢这个。也可以轻松地进行通用化,以便您可以传入错误类型:fun &lt;T:Exception&gt; Throwable.getCustomException(classType: Class&lt;T&gt;): T?,然后将 GSON 行更新为Gson().fromJson(errorJsonString, classType)。用作e.getCustomException(CustomException::class.java)?【参考方案13】:
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());

【讨论】:

这工作.. 与 422 的 laravel api 响应 这是唯一适用于我的改造 2.9.0【参考方案14】:

如果您只注入从 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();
    
  

【讨论】:

【参考方案15】:

这似乎是你使用 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(); 


【讨论】:

【参考方案16】:

已经有很多有效的答案了。这只是一个用例的补充,当您需要多次使用相同的改造响应时。以下两个都不能使用,如you can read response body only once,因为它会在之后关闭,并且您每次尝试从同一个响应对象中读取时都会得到null

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

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

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

【讨论】:

你救了我的命! THX【参考方案17】:

经过测试并且可以工作

 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;
        

【讨论】:

【参考方案18】:

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

【讨论】:

【参考方案19】:

解决了:

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

【讨论】:

如何通过 GsonConverterFactory 进行转换?有什么想法吗? 我找到了方法。我只是将 JacksonConverterFactory 更改为 GsonConverterFactory 它会将 json 转换为我的自定义对象,但它会发出警告 Unchecked cast retrofit.Converter> MyError 类有什么?【参考方案20】:
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();
            

【讨论】:

【参考方案21】:

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

【讨论】:

【参考方案22】:

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

【讨论】:

【参考方案23】:
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)`

【讨论】:

【参考方案24】:

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

【讨论】:

【参考方案25】:

非常简单。这救了我的命

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

【讨论】:

【参考方案26】:

如果您的错误响应是一个字符串,您可以使用以下 kotlin 代码对其进行反序列化:

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

【讨论】:

以上是关于Retrofit 2.0如何获得反序列化错误response.body的主要内容,如果未能解决你的问题,请参考以下文章

Retrofit 在 JSON 反序列化的时候提示 UnrecognizedPropertyException 异常

Retrofit 2.0 - 如何获取 400 Bad Request 错误的响应正文?

反序列化的改造/杰克逊错误

改造:用杰克逊反序列化失败,没有任何错误

如何处理 Retrofit 2.0 中的错误

如何使用Retrofit2,RxJava2,Gson TypeAdapterFActory正确映射Gson?