Retrofit 2 - Define Custom GSON Converter Factory

Retrofit is a very popular HTTP client library for Android. Using Retrofit makes it easy to parse API response and use it in your application. It has built-in GSON converter that can automatically parse HTTP response into an Object or any other types in Java that can be used in your code. The problem is if we have custom response, it can't parse using the buillt-in converter. The solution is defining a custom factory converter according to the response format. This tutorial gives you examples how to do it.

The Default GSON Converter

For example, we have a simple class Item which has three variables id, name and amount.

  public class Item {
      @SerializedName("id")
      private String id;
      @SerializedName("name")
      private String name;
      @SerializedName("amount")
      private int amount;

      public Item(String id, String name, intt amount) {
          this.id = id;
          this.name = name;
          this.amount = amount;
      }

      public String getId() {
          return id;
      }

      public void setId(String id) {
          this.id = id;
      }

      public String getName() {
          return name;
      }

      public void setName(String name) {
          this.name = name;
      }

      public int getAmount() {
          return amount;
      }

      public void setAmount(int amount) {
          this.amount = amount;
      }

We have two API endpoints, first for getting the details of an item and the other is for getting the list of items. We define an interface for the service along with the endpoints.

  public interface ItemService {
      @GET("/api/item/{id}")
      Call<Item> getItemDetails(
@Path("id") String id
); @GET("/api/item") Call<List<Item>> getItemList();
}

Somewhere, we create a Retrofit instance using the default GsonConverterFactory.

  import retrofit2.Retrofit;
  import retrofit2.converter.gson.GsonConverterFactory;

  ...

  Retrofit retrofit = new retrofit2.Retrofit.Builder()
                      .baseUrl(BASE_URL)
                      .addConverterFactory(GsonConverterFactory.create())
                      .build();

If the API response for getting item details and item list is a JSON object representing the item and JSON array consisting of JSON objects respectively, the above code will work.

Get Item Details Response

  {
    id: '00000001'
    name: 'Item 1',
    amount: 10
  }

Get Item List Response

  [
    {
      id: '00000001'
      name: 'Item 1',
      amount: 10
    },
    {
      id: '00000002'
      name: 'Item 2',
      amount: 15
    },
  ]

However, in most cases, the API response is not in the above formats. For example, we have the following formats:

Get Item Details Custom Response

  {
    success: true,
    data:  {
      id: '00000001'
      name: 'Item 1',
      amount: 10
    }
  }

Get Item List Custom Response

  {
    success: true,
    data: [
      {
        id: '00000001'
        name: 'Item 1',
        amount: 10
      },
      {
        id: '00000002'
        name: 'Item 2',
        amount: 15
      },
    ]
}

GsonConverterFactory is unable to automatically parse the JSON to find the node where the data exists. Therefore we have to create custom converters.

Using Custom GSON Converter Factory

To make Retrofit able to parse custom response, we need to define custom deserializers for parsing HTTP response. Basically, we create a function that parses the HTTP response and returns an object, a list or any other types supported in Java.

GetItemDetailsDeserializer.java

  package com.woolha.example.network.deserializers.Item;  

  import com.google.gson.JsonArray;
  import com.google.gson.JsonDeserializationContext;
  import com.google.gson.JsonDeserializer;
  import com.google.gson.JsonElement;
  import com.google.gson.JsonObject;
  import com.google.gson.JsonParseException;

  import java.lang.reflect.Type;
  import java.util.ArrayList;
  import java.util.List;

  import com.woolha.example.models.Item;

  public class GetItemDetailsDeserializer implements JsonDeserializer<Item> {
      @Override
      public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
          final JsonObject jsonObject = json.getAsJsonObject();
          
          final String id = jsonObject.get("id").getAsString();
          final String name = jsonObject.get("name").getAsString();
          final int amount active = jsonObject.get("amount").getAsInteger();
  
          return new Item(id, name, amount);
      }
  }

GetItemListDeserializer.java

  package com.woolha.example.network.deserializers.Item;  

  import com.google.gson.JsonArray;
  import com.google.gson.JsonDeserializationContext;
  import com.google.gson.JsonDeserializer;
  import com.google.gson.JsonElement;
  import com.google.gson.JsonObject;
  import com.google.gson.JsonParseException;

  import java.lang.reflect.Type;
  import java.util.ArrayList;
  import java.util.List;

  import com.woolha.example.models.Item;

  public class GetItemListDeserializer implements JsonDeserializer<List<Item>> {
      @Override
      public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
          List items = new ArrayList<>();
  
          final JsonObject jsonObject = json.getAsJsonObject();
          final Boolean success = jsonObject.get("success").getAsBoolean();
          final JsonArray itemsJsonArray = jsonObject.get("data").getAsJsonArray();
  
          for (JsonElement itemsJsonElement : itemsJsonArray) {
              final JsonObject itemJsonObject = itemsJsonElement.getAsJsonObject();
              final String id = itemJsonObject.get("id").getAsString();
              final String name = itemJsonObject.get("name").getAsString();
              final int amount = itemJsonObject.get("amount").getAsInteger();

              items.add(new Item(id, name, amount));
          }
  
          return items;
      }
  }

We need to slightly modify the way how we create a Retrofit instance. To make the code reusable, it's better to create a new class for generating Retrofit instance.

  import retrofit2.Retrofit;
  import retrofit2.converter.gson.GsonConverterFactory;

  ...

  public class RetrofitClientInstance {
      private static final String BASE_URL = "http://xxx.xxx.xxx.xxx";

      private static Converter.Factory createGsonConverter(Type type, Object typeAdapter) {
          GsonBuilder gsonBuilder = new GsonBuilder();
          gsonBuilder.registerTypeAdapter(type, typeAdapter);
          Gson gson = gsonBuilder.create();

          return GsonConverterFactory.create(gson);
      }

      public static Retrofit getRetrofitInstance(Type type, Object typeAdapter) {
          return new retrofit2.Retrofit.Builder()
                  .baseUrl(BASE_URL)
                  .addConverterFactory(createGsonConverter(type, typeAdapter))
                  .build();
      }
  }

Then, use getRetrofitInstance anywhere in your code where you need to call the API.

Example.java

  ItemService itemService = RetrofitClientInstance
                .getRetrofitInstance(new TypeToken<List<Item>>() {}.getType(), new GetItemListDeserializer())
                .create(ItemService.class);
  Call<List<Item>> call = itemService.getItemList();

Example2.java

  ItemService itemService = RetrofitClientInstance
                .getRetrofitInstance(Item.class, new GetItemDetailsDeserializer())
                .create(ItemService.class);
  Call<Item> call = itemService.getItemDetails();

That's how to do it. Using custom GSON converter factory, it's possible to parse HTTP response with various formats. Thanks for reading this tutorial.