How to handle several possible response types using Retrofit2, Gson, and Rx

The API I have to use is sucks and always returns HTTP 200. But sometimes there is a correct answer:

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

and sometimes an error occurs:

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

I am using Retrofit2 with a Gson converter and an 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 get an error response, the onError handler onError called with the 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 this? If I could get the answer in the onError handler, I could revise it with the corresponding error model class. But it seems I can’t get a crude answer.

+6
source share
1 answer

You can use your own Gson deserializer to marshal both responses into one type of object. Here is a rough sketch of an idea assuming that your current response type is List<Map<String, String>> , you will need to adjust depending on your actual return type. I also believe that the API always returns an array with 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 custom Gson

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

use this in your modified builder -

 .addConverterFactory(GsonConverterFactory.create(gson)) 

You must also update your interface to return an Observable<MyResponse> . Now you will get both success and error in onNext . You will need to check the object to determine if it is a successful response ( response != null ) or not.

+2
source

Source: https://habr.com/ru/post/1014142/


All Articles