Retrofit2 Handle condition when status code 200 but json structure different than datamodel class

后端 未结 5 451
-上瘾入骨i
-上瘾入骨i 2021-02-01 22:09

I\'m using Retrofit2 and RxJava2CallAdapterFactory.

The API I consume returns status code always as 200 and for success and response JSON string the JSON st

5条回答
  •  时光取名叫无心
    2021-02-01 22:49

    So you have two different successful (status code 200) responses from the same endpoint. One being the actual data model and one being an error (both as a json structure like this?:

    Valid LoginBean response:

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

    Error response

    {
      "error": "error message"
    }
    

    What you can do is have an entity that wraps both cases and use a custom deserializer.

    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
    }
    

    A wrapper for the error:

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

    Then you need a JsonDeserializer:

    public class LoginBeanResponseDeserializer implements JsonDeserializer {
    
      @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);
        }
      }
    }
    

    Then add this deserializer to the 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();
    

    This is the only way I can think of making this work. But as already mentioned this kind of API design is just wrong because status codes are there for a reason. I still hope this helps.

    EDIT: What you can then do inside the class where you make the call to that Retrofit (if you already converted from Call to Single with RxJava) is actually return a proper error. Something like:

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

提交回复
热议问题