source

수정 2.0: 역직렬화된 오류 응답을 얻는 방법.몸

goodcode 2022. 8. 28. 09:37
반응형

수정 2.0: 역직렬화된 오류 응답을 얻는 방법.

Retrofit 2.0.0-beta1을 사용하고 있습니다.

테스트에서는 대체 시나리오가 있으며 오류 HTTP 400이 예상됩니다.

나는 가지고 싶다retrofit.Response<MyError> response그렇지만response.body() == null

My Error는 역직렬화되지 않았습니다.여기서만 표시됩니다.

response.errorBody().string()

MyError를 개체로 제공하지 않습니다.

저는 현재 컨버터나 특수 클래스를 사용할 필요가 없는 매우 쉬운 구현을 사용하고 있습니다.사용하고 있는 코드는 다음과 같습니다.

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()는 올바른 값을 한 번만 반환합니다.다시 호출하면 빈 문자열이 반환됩니다.따라서 이 값을 재사용할 경우 첫 번째 콜과 함께 변수에 저장합니다.

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

그것은 사실 매우 직설적이다.

코틀린:

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

retrofit: 2.5.0에서 테스트 완료. charStream에서 문자열을 제공하는 텍스트를 읽은 다음 JSONObject로 구문 분석합니다.

아디오스

Retrofit 2.0 베타2에서는 다음과 같이 에러 응답이 표시됩니다.

  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 베타3 업데이트:

  1. 동기 - 변경되지 않음
  2. Asynchronous - 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
        }
    });
    

에러 응답 및 사용자 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();
}
 @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/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();
            }
        }
    }

Kotlin을 사용하는 경우 응답 클래스에 대한 확장 함수를 만드는 다른 솔루션이 있을 수 있습니다.

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

나도 같은 문제에 직면해 있었다.복고풍으로 해결했어요.이거 보여드릴게요.

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

이제 우리는 우리 아들을 다룰 수 있는 수업이 필요하다.

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

이제 retrofit 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) {

        }
    });
}

이제 토스트를 보여줄 수 있어

다음은 다음 중 하나를 사용하는 우아한 솔루션입니다.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)

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

이것은 Retrofit과 함께 OkHttp를 사용할 때 발생하는 문제인 것 같습니다.따라서 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(); 
}
}

이미 유효한 답변이 많이 있습니다.이는 동일한 Retrofit 응답을 두 번 이상 사용해야 하는 경우에 대한 추가 사항일 뿐입니다.다음 중 어느 것도 사용할 수 없습니다.응답 본문은 한 번만 읽을 수 있기 때문에 나중에 닫히고 다음 메시지가 표시됩니다.null다음 번에는 같은 응답 객체에서 읽으려고 할 때 다음을 수행합니다.

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

대신에, 응답 문자열의 읽기 전용 카피를 취득할 수 있습니다(응답 자체는 건네져 나중에 소비할 수 있습니다).

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

테스트 완료 및 동작

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

에러 응답이 문자열인 경우는, 다음의 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")
        }
    }
}

해결 방법:

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

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

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

아주 간단해. 그리고 이게 내 목숨을 구했어.

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

retrofit 오류의 경우 Response, 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);
            }
        }

언급URL : https://stackoverflow.com/questions/32519618/retrofit-2-0-how-to-get-deserialised-error-response-body

반응형