How to handle multiple possible response types with Retrofit2, Gson and Rx

試著忘記壹切 提交于 2019-12-08 16:40:52

问题


The API i have to use sucks, and always returns HTTP 200. But sometimes there is proper response:

[{"blah": "blah"}, {"blah": "blah"}]

and sometimes, there is error:

{"error": "Something went wrong", "code": 123}

I'm using Retrofit2 with Gson converter and Rx adapter:

final Api api = new Retrofit.Builder()
        .baseUrl(URL)
        .client(client)
        .addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(Api.class);

And now, when I receive error response, the onError handler is called with following exception:

java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
    at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
    at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
    at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
    at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
    at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
    at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
    at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
    at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
    at rx.Subscriber.setProducer(Subscriber.java:211)
    at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
    at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
    at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
    at rx.Observable.unsafeSubscribe(Observable.java:10144)
    at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
    at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:230)
    at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
    at java.lang.Thread.run(Thread.java:761)

How can I solve it? If I could get the response in the onError handler, I could reparse it with proper error model class. But it seems I can't get the raw response.


回答1:


You can use a custom Gson deserializer to marshal both responses into a single object type. Here is a rough sketch of the idea assuming your current response type is List<Map<String, String>>, you will need to adjust based on your actual return type. I am also making the assumption that the API always returns an array on success --

public class MyResponse {
  String error;
  Integer code;
  List<Map<String, String>> response;
}

interface MyApi {
  @GET("/")
  Observable<MyResponse> myCall();
}

private class MyResponseDeserializer implements JsonDeserializer<MyResponse> {
  public MyResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
      throws JsonParseException {
    MyResponse response = new MyResponse();
    if (json.isJsonArray()) {
      // It is an array, parse the data
      Type responseType = new TypeToken<List<Map<String, String>>>(){}.getType();
      response.response = context.deserialize(json, responseType);
    } else {
      // Not an array, parse out the error info
      JsonObject object = json.getAsJsonObject();
      response.code = object.getAsJsonPrimitive("code").getAsInt();
      response.error = object.getAsJsonPrimitive("error").getAsString();
    }
    return response;
  }
}

Use the above to create a custom Gson

Gson gson = new GsonBuilder()
    .registerTypeAdapter(MyResponse.class, new MyResponseDeserializer())
    .create();

use that in your retrofit builder --

.addConverterFactory(GsonConverterFactory.create(gson))

You should also update your interface to return Observable<MyResponse>. You will get both success and error in onNext now. You'll need to inspect the object to determine if it is a successful response (response != null) or not.



来源:https://stackoverflow.com/questions/41693087/how-to-handle-multiple-possible-response-types-with-retrofit2-gson-and-rx

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!