Retrofit 2 RxJava - Gson - "Global" deserialization, change response type

I use an API that always returns a JSON object that looks like this:

public class ApiResponse<T> { public boolean success; public T data; } 
Field

data strong> is a JSON object that contains all the valuable information. Of course, these are different for different requests. Therefore, my modified interface looks like this:

 @GET(...) Observable<ApiResponse<User>> getUser(); 

And when I want to process the answer, I need to do, for example:

 response.getData().getUserId(); 

I really don't need this boolean success field, and I would like to omit it, so my interface for modification might look like this:

 @GET(...) Observable<User> getUser(); 

Is it possible to do this in Gson? Or maybe a neat Rx function that automatically converts this?

EDIT: json example:

 { "success": true, "data": { "id": 22, "firstname": "Jon", "lastname": "Snow" } } 

EDIT 2: I was able to do this using cross-conversion, where I manually modify the response body. It works, but if you have other suggestions, please write them :)

+3
json android gson retrofit rx-java
Dec 15 '15 at 12:07
source share
2 answers

As was said, the solution with the interceptor is not so great. I managed to solve this problem with an Rx transformer. I also added a special api exception that I can throw when something goes wrong and can easily handle this in an onError. I think it is more reliable.

Response Wrapper:

 public class ApiResponse<T> { private boolean success; private T data; private ApiError error; } 

The error object is returned when success is false:

 public class ApiError { private int code; } 

Throw this exception if success is incorrect:

 public class ApiException extends RuntimeException { private final ApiError apiError; private final transient ApiResponse<?> response; public ApiException(ApiResponse<?> response) { this.apiError = response.getError(); this.response = response; } public ApiError getApiError() { return apiError; } public ApiResponse<?> getResponse() { return response; } } 

And the transformer:

 protected <T> Observable.Transformer<ApiResponse<T>, T> applySchedulersAndExtractData() { return observable -> observable .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(tApiResponse -> { if (!tApiResponse.isSuccess()) throw new ApiException(tApiResponse); else return tApiResponse.getData(); }); } 
+6
Dec 16 '15 at 8:42
source share

As far as I know, there is no easy solution to achieve what you want. An easy decision, I mean something more reasonable than what you have now. You can deserialize all the data yourself, you can strip data with interceptors, but this is a lot more effort that just uses the API as it is now.

Also, think that if it changes, and some field apperas next to this element and boolean (for example, a long recent update), you will have to change everything.

The only reasonable idea is to wrap your Api interface with another class and inside it call .map on your Observable to convert ApiResponse<T> to T

+1
Dec 15 '15 at 18:02
source share



All Articles