Custom converter for Retrofit 2
I was looking for a simple example about how to implement a custom converter for Retrofit 2. Unfortunately found none.
I found this example but, at least for me, it's too complicated for my purpose.
Happilly, I found a solution.
This solution is to use GSON deserializers
.
We don't need to create a custom converter, we just have to customize the GSON converter
.
Here is a great tutorial. And here is the code I used to parse the JSON described in my question:
Login Deserializer: Defines how to parse the JSON as an object of our target class (using conditionals and whatever we need).
Custom GSON converter: Builds a GSON converter that uses our custom deserializer.
How make a custom Retrofit ConverterFactory to transform ReponseBody to a new ResponseBody?
I finally managed to solve the problem using an interceptor in okhttp. This is the code:
class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
ResponseBody body = response.body();
if (body.contentType().equals(MediaType.parse("text/csv"))) {
final CharBuffer todo = Charset.forName("windows-1252").decode(ByteBuffer.wrap(body.bytes()));
body = ResponseBody.create(body.contentType(), todo.toString());
Response.Builder builder = response.newBuilder();
return builder.headers(response.headers())
.body(body)
.build();
}
return response;
}
}
And this just before Retrofit build.
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.addInterceptor(new LoggingInterceptor());
Custom converter for Retrofit 2
I was looking for a simple example about how to implement a custom converter for Retrofit 2. Unfortunately found none.
I found this example but, at least for me, it's too complicated for my purpose.
Happilly, I found a solution.
This solution is to use GSON deserializers
.
We don't need to create a custom converter, we just have to customize the GSON converter
.
Here is a great tutorial. And here is the code I used to parse the JSON described in my question:
Login Deserializer: Defines how to parse the JSON as an object of our target class (using conditionals and whatever we need).
Custom GSON converter: Builds a GSON converter that uses our custom deserializer.
Retrofit: Custom ConverterFactory only for response
OK... simple solution was to override the requestBodyConverter
@Override
public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return factory.requestBodyConverter(type, parameterAnnotations, methodAnnotations, retrofit);
}
factory
is a global variable (GsonConverterFactory) which is set on the constructor
Retrofit Gson custom json generic converter
When you specify return type in Retrofit's client it's passed to Retrofit's converter as Type and then Gson receives that type which will be your ApiResponse<RegisterResponseData>
. From that point Gson will understand that data
is of type RegisterResponseData
and will produce your model object.
Just try it without your ApiResponseDeserializer
and you'll see it's working.
Edit:
Answering your additional question in comments:
If you want to skip your "body" object in json you can write your wrapper object like this:
public class ApiResponse<T> {
@SerializedName("body")
private ApiResponseBody<T> body;
public ApiResponse() {
}
public ApiResponse(ApiData<T> body) {
this.body = body;
}
}
public class ApiResponseBody<T> {
@SerializedName("data")
private T data;
@SerializedName("errors")
private Map<String, List<String>> errors;
public ApiResponseBody() {
}
public ApiResponseBody(T data, Map<String, List<String>> errors) {
this.data = data;
this.errors = errors;
}
}
And use it in usual way
@POST("users/signup")
Single<ApiResponse<RegisterResponseData>> register(@Body RegisterRequest request);
Retrofit custom converter factory for list type
Since type
in the parameters is the type of the Call
, the correct check to skip non-List<Item>
cases was -
if (!type.equals(Types.newParameterizedType(List.class, Item.class)))
return null;
This allows the next converter to process the response if the type of the call is not List<Item>
.
Custom Converter for Retrofit
I would do something like that:
public class StringList extends ArrayList<String> {
// Empty on purpose. The class is here only to be recognized by Gson
}
public class CitationMain {
@SerializedName("field_name_here")
StringList values;
// Your other fields
}
Then when creating the RestAdapter:
public class StringListDeserializer implements JsonDeserializer<StringList> {
@Override
public StringList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext
context) throws JsonParseException {
StringList value = new StringList();
if (json.isJsonArray()) {
for (JsonElement element : json.getAsJsonArray()) {
value.add(element.getAsString());
}
} else {
value.add(json.getAsString());
}
return value;
}
}
And then:
Gson gson = new GsonBuilder()
.registerTypeAdapter(StringList.class, new StringListDeserializer())
.create();
RestAdapter.Builder builder = new RestAdapter.Builder()
//...
.setConverter(new GsonConverter(gson));
It's not ideal, since the object is custom, but any other solution that I can think of right now is significantly more complex.
The deserializer here is registered specifically for the fields declared as StringList
, and will handle the case of a single string as well as the case of a string array. Any other field type will use the default deserialization process.
Related Topics
How to Change the Tabs Images in the Tabhost
Android:How to Update the Selector(Statelistdrawable) Programmatically
Android Image View Matrix Scale + Translate
Clear Text in Edittext When Entered
How to Change the Background Color Around a Dialogfragment
How to Attach Android Source to Eclipse
Android Studio: Application Installation Failed
Android - How to Check Proguard Obfuscation Has Worked
Add Elevation/Shadow on Toolbar for Pre-Lollipop Devices
Remove Bottomnavigationview Labels
How to Do Circular Scrolling on Viewpager
Android: Running a Background Task Using Alarmmanager
How to Change the Color of the Tabs Indicator Text in Android
How to Automatically Uninstall Android App from Device Before Installing a New Version
Using Android.Support.V7.Widget.Cardview in My Project (Eclipse)
Update Text of Notification, Not Entire Notification
How to Get Each Device's Ip Address in Wi-Fi Direct Scenario
Android Kitkat 4.4 Hangouts Cannot Handle Sending Sms Intent