Custom Converter for Retrofit 2

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



Leave a reply



Submit