当前位置: 首页 > 面试题库 >

Retrofit2处理状态码为200但json结构与datamodel类不同时的条件

訾俊名
2023-03-14
问题内容

我正在使用Retrofit2和RxJava2CallAdapterFactory

我使用的API的返回状态代码始终为200,对于成功和响应JSON字符串,JSON结构完全不同。由于状态码始终为200,因此始终调用onResponse()方法。因此,在错误情况下,我无法从JSON中提取错误消息。

解决方案1:

ScalarsConverterFactory用来获取响应String并手动使用Gson来解析响应。如何在不使用GSON或android中任何其他库的情况下使用改造获得String响应

该解决方案的问题 :我打算使用RxJava2CallAdapterFactory来使改装方法返回DataModel类。

我需要找到针对此问题的最佳解决方案,以这种方式,我可以继续从Retrofit方法返回数据模型类,并以某种方式从响应中识别错误条件(确定响应JSON与数据模型不匹配),然后解析错误JSON转换为数据模型。

改造客户

 public static Retrofit getClient(String url) {
        if (apiClient == null) {

            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();

            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor).build();
            apiClient = new Retrofit.Builder()
                    .baseUrl(url)
                    /*addCallAdapterFactory for RX Recyclerviews*/
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    /* add ScalarsConverterFactory to get json string as response */
//                    .addConverterFactory(ScalarsConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
//                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .client(httpClient)
                    .build();
        }
        return apiClient;
    }

方法

public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
        baseUrl = AppPreference.getParam(UiUtils.getContext(), SPConstants.BASE_URL, "").toString();
        ApiInterface apiService =
                ApiClient.getClient(baseUrl).create(ApiInterface.class);

        Call<LoginBean> call = apiService.getLoginResponse(queryParams);
        call.enqueue(new Callback<LoginBean>() {

            @Override
            public void onResponse(Call<LoginBean> call, Response<LoginBean> response) {

                if (response.body().isObjectNull()) {
                    httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
                            HttpCallback.RETURN_TYPE_FAILURE, 0, null);
                    return;
                }
                httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
                        HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body());
            }





        @Override
        public void onFailure(Call<LoginBean> call, Throwable t) {
            // Log error here since request failed
            httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
                    HttpCallback.RETURN_TYPE_FAILURE, 0, t);
            t.printStackTrace();
        }
    });
}

接口

@GET("App/login")
Call<LoginBean> getLoginResponse(@QueryMap Map<String, String> queryMap);

PS:该API目前无法更改,因为其他一些应用程序正在使用它。

  • Gson解析器不会返回空对象实例,以使我理解json结构和数据模型不匹配。

  • RestAdapter在Retrofit 2中已弃用

我正在寻找解决此问题的最佳方法,最好避免手动进行json解析,并充分利用改造和RX适配器的优势。

编辑

因此,响应码200

  1. response.isSuccessful() == true

  2. response.body() != null 因为Gson从不创建空实例或在JSON结构不匹配时抛出任何异常,所以也是如此

  3. response.errorBody() == null 始终作为响应从服务器作为输入流发送。

    if (response.isSuccessful() && response.body() != null) {
    //control always here as status code 200 for error condition also
    

    }else if(response.errorBody()!=null){
    //control never reaches here
    }

编辑2

该解决方案基于anstaendig答案

  • 我创建了一个基本的通用类来进一步解决这个问题。
  • 由于我有多个API和数据模型,因此我必须为每个API创建解串器

基础API标记

public class BaseApiBean<T> {

    @Nullable
    private T responseBean;

    @Nullable
    private ErrorBean errorBean;

    public BaseApiBean(T responseBean, ErrorBean errorBean) {
        this.responseBean = responseBean;
        this.errorBean = errorBean;
    }

    public T getResponseBean() {
        return responseBean;
    }

    public void setResponseBean(T responseBean) {
        this.responseBean = responseBean;
    }

    public ErrorBean getErrorBean() {
        return errorBean;
    }

    public void setErrorBean(ErrorBean errorBean) {
        this.errorBean = errorBean;
    }
}

基本解串器

  public abstract class BaseDeserializer implements JsonDeserializer<BaseApiBean> {


        @Override
        public BaseApiBean deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            // Get JsonObject
            final JsonObject jsonObject = json.getAsJsonObject();
            if (jsonObject.has("result")) {
                   /* {"result":"404"}*/
                ErrorBean errorMessage = new Gson().fromJson(jsonObject, ErrorBean.class);
                return getResponseBean(errorMessage);
            } else {

                return getResponseBean(jsonObject);
            }
        }

        public abstract BaseApiBean getResponseBean(ErrorBean errorBean);
        public abstract BaseApiBean getResponseBean(JsonObject jsonObject);
    }

每个API的自定义反序列化器

public class LoginDeserializer extends BaseDeserializer {


    @Override
    public BaseApiBean getResponseBean(ErrorBean errorBean) {
        return new LoginResponse(null, errorBean);
    }

    @Override
    public BaseApiBean getResponseBean(JsonObject jsonObject) {

        LoginBean loginBean = (new Gson().fromJson(jsonObject, LoginBean.class));
        return new LoginResponse(loginBean, null);
    }
}

习惯回应豆

public class LoginResponse extends BaseApiBean<LoginBean> {
    public LoginResponse(LoginBean responseBean, ErrorBean errorBean) {
        super(responseBean, errorBean);
    }

}

客户

public class ApiClient {
    private static Retrofit apiClient = null;
    private static Retrofit apiClientForFeedBack = null;
    private static LoginDeserializer loginDeserializer = new LoginDeserializer();
    private static AppVerificationDeserializer appVerificationDeserializer = new AppVerificationDeserializer();


    public static Retrofit getClient(String url) {
        if (apiClient == null) {

            GsonBuilder gsonBuilder=new GsonBuilder();
            gsonBuilder.registerTypeAdapter(LoginResponse.class,
                    loginDeserializer);
            gsonBuilder.registerTypeAdapter(AppVerificationResponse.class,
                    appVerificationDeserializer);
            Gson gson= gsonBuilder.create();


            HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();

            interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(interceptor)
                    .retryOnConnectionFailure(true)
                    .connectTimeout(15, TimeUnit.SECONDS)
                    .build();

            apiClient = new Retrofit.Builder()
                    .baseUrl(url)
                    /*addCallAdapterFactory for RX Recyclerviews*/
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    /* add ScalarsConverterFactory to get json string as response */
//                    .addConverterFactory(ScalarsConverterFactory.create())
//                    .addConverterFactory(GsonConverterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .client(httpClient)
                    .build();
        }
        return apiClient;
    }

处理响应

 public static void getLoginAPIResponse(String username, String password, String sourceId, String uuid, final HttpCallback httpCallback) {
        baseUrl = AppPreference.getParam(getContext(), SPConstants.MT4_BASE_URL, "").toString();
        ApiInterface apiService =
                ApiClient.getClient(baseUrl).create(ApiInterface.class);
        HashMap<String, String> queryParams = new HashMap<>();
        queryParams.put(APIConstants.KEY_EMAIL, sourceId + username.toLowerCase());
        queryParams.put(APIConstants.KEY_PASSWORD, Utils.encodePwd(password));


        Call<LoginResponse> call = apiService.getLoginResponse(queryParams);
        call.enqueue(new Callback<LoginResponse>() {

            @Override
            public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {

                if (response.body().getResponseBean()==null) {
                    httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
                            HttpCallback.RETURN_TYPE_FAILURE, 0,  response.body().getErrorBean());
                    return;
                }
                httpCallback.resultCallback(APIConstants.API_LOGIN, HttpCallback.REQUEST_TYPE_GET,
                        HttpCallback.RETURN_TYPE_SUCCESS, response.code(), response.body().getResponseBean());

            }


            @Override
            public void onFailure(Call<LoginResponse> call, Throwable t) {
                // Log error here since request failed
                httpCallback.resultCallback(APIConstants.API_APP_VERIFICATION, HttpCallback.REQUEST_TYPE_GET,
                        HttpCallback.RETURN_TYPE_FAILURE, 0, t);
                t.printStackTrace();
            }
        });
    }

问题答案:

因此,您从同一端点有两个不同的成功响应(状态码200)。一个是实际的数据模型,另一个是错误(都像这样的json结构?:

有效的LoginBean响应:

{
  "id": 1234,
  "something": "something"
}

错误回应

{
  "error": "error message"
}

您可以做的是拥有一个包装两种情况并使用自定义反序列化器的实体。

class LoginBeanResponse {
  @Nullable private final LoginBean loginBean;
  @Nullable private final ErrorMessage errorMessage;

  LoginBeanResponse(@Nullable LoginBean loginBean, @Nullable ErrorMessage errorMessage) {
    this.loginBean = loginBean;
    this.errorMessage = errorMessage;
  }
  // Add getters and whatever you need
}

错误的包装器:

class ErrorMessage {
  String errorMessage;
  // And whatever else you need
  // ...
}

然后,您需要一个JsonDeserializer

public class LoginBeanResponseDeserializer implements JsonDeserializer<LoginBeanResponse> {

  @Override
  public LoginBeanResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

    // Based on the structure you check if the data is valid or not
    // Example for the above defined structures:

    // Get JsonObject
    final JsonObject jsonObject = json.getAsJsonObject();
    if (jsonObject.has("error") {
      ErrorMessage errorMessage = new Gson().fromJson(jsonObject, ErrorMessage.class);
      return new LoginBeanResponse(null, errorMessage)
    } else {
      LoginBean loginBean = new Gson().fromJson(jsonObject, LoginBean.class):
      return new LoginBeanResponse(loginBean, null);
    }
  }
}

然后将此反序列化器添加到中GsonConverterFactory

GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(LoginBeanResponse.class, new LoginBeanResponseDeserializer()).create():

apiClient = new Retrofit.Builder()
    .baseUrl(url)
    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
    .addConverterFactory(GsonConverterFactory.create(gsonBuilder))
    .client(httpClient)
    .build();

这是我可以想到的唯一方法。但是,正如已经提到的,这种API设计是错误的,因为存在状态代码是有原因的。我仍然希望这会有所帮助。

编辑:然后,您可以在调用Retrofit的类中做什么(如果您已经使用RxJava
从转换Call<LoginBeanResponse>Single<LoginBeanResponse>)实际上返回了正确的错误。就像是:

Single<LoginBean> getLoginResponse(Map<String, String> queryMap) {
    restApi.getLoginResponse(queryMap)
        .map(loginBeanResponse -> { if(loginBeanResponse.isError()) {
            Single.error(new Throwable(loginBeanResponse.getError().getErrorMessage()))
        } else { 
            Single.just(loginBeanReponse.getLoginBean()) 
        }})
}


 类似资料:
  • 我目前正在开发一个Java11 Spring Boot应用程序。在一个完全的未来中,我向网络服务发射了一大堆REST请求。其中只有一个(似乎是随机的)会导致TimeoutExc0019,尽管它后面跟着一个成功的状态200 OK在日志中收到响应。以下(匿名)日志条目: 有人有同样的经历吗?我看不出成功完成的请求和“失败”的请求有什么不同。

  • 我使用Spring中的webClient从GraphQL WebService中获取数据。此代码位于一个泛型方法中,该方法将响应体解码为不同类型的对象。如果响应是预期的类型,则它运行良好。 现在我想处理来自WebService的错误。因此,我的问题与此非常相似:Spring Webflux:Webclient:Get body on error我发现建议使用onStatus,但我的问题是,即使re

  • 目前,我在主activity类和widget类中都声明了一个AsyncTask。除了中的结果处理之外,其他代码都是一样的(在activity中,检索到的结果转到activity中的textview,而在小部件中,它转到my widget中的textview)。 我想将移动到一个外部java文件中(以避免代码重复),并通过不同的结果处理方式从其他不同的类(如我的小部件和main activity)调

  • 本文向大家介绍HTTP 状态码 304和 200 的区别?相关面试题,主要包含被问及HTTP 状态码 304和 200 的区别?时的应答技巧和注意事项,需要的朋友参考一下 参考回答: 状态码200:请求已成功,请求所希望的响应头或数据体将随此响应返回。即返回的数据为全量的数据,如果文件不通过GZIP压缩的话,文件是多大,则要有多大传输量。 状态码304:如果客户端发送了一个带条件的 GET 请求且

  • 我从服务器得到以下响应:状态代码。没有实际响应(返回的对象等),因此不需要创建POJO类。 所以,我不知道如何在不创建POJO类的情况下处理这个状态代码。有没有任何选项可以使写代码不使用POJO类?

  • 我们正在将AngularJS应用程序迁移到最新的Angular版本。我们总是通过接收状态代码200来处理http请求,如果有错误,我们仍然得到状态代码200(不包括404-500,以及关于实际服务器或用户连接的错误)。例如,如果用户令牌过期,我们发出请求,我们将得到一个状态代码200,其主体为errorcode:number,message:string。这是不会改变的。 使用关于令牌的相同示例,