{{announcement.body}}
{{announcement.title}}

Consuming Variable Responses in Gson

DZone 's Guide to

Consuming Variable Responses in Gson

Learn more about using Gson to handle variable response structures.

· Java Zone ·
Free Resource

Computer with code next to coffee cup

Learn more about using Gsonhow to handle variable response structures. 

No matter public or private, Restful APIs are the most popular way to integrate our applications with the world outside. This means you do not have a chance to alter the services you consume; instead, you should adapt your code most of the time.

Since Java is a static-typed language, it can be a challenge while you are consuming and mapping the data into your model objects, especially if the response data changes depending on the use-case.

You may also like: Gson: Deserialization of Generic Types

Today, we will explain how to handle variable response structures with the Gson library.

Maven Dependency

Before we start, we need to add the Gson dependency into our project's pom.xml:

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.5</version>
</dependency>


Sample Response

Let's pick a sample API for a blog which serves detailed information about its articles along with the comments which may be more than one:

{
    "id": 1,
    "name": "sample article",
    "comments": [
        {"id":1, "text":"some comment text"},
        {"id":2, "text":"some another comment text"}
    ]
}


However, if there is only one single comment, then its structure changes into object hash form:

{
    "id": 1,
    "name": "sample article",
    "comments": {"id":1, "text":"some comment text"}
}


Writing a Response Model

In order to map the data, we need a POJO as a response model. Let's start by defining a simple class that matches the main response structure:

public class ArticleModel {

    @Expose
    private Long id;

    @Expose
    private String name;

    @Expose
    private List<CommentModel> comments;

    // standard getters and setters

}


Next, we need to define another class corresponds to comment response structure:

public class CommentModel {

    @Expose
    private Long id;

    @Expose
    private String text;

    // standard getters and setters

}


Consuming the Response

Since we use List<CommentModel> for the comments field, we can easily map multiple list values because Gson already does the heavy lifting for us:

String responseText = "{
  "id": 1,
  "name": "sample article",
  "comments": [
    {"id":1, "text":"some comment"},
    {"id":2, "text":"some another comment"}
  ]}";
Gson gson = new Gson();
ArticleModel model = gson.fromJson(responseText, ArticleModel.class);

Assert.assertEquals(1, model.getId().longValue());
Assert.assertEquals("sample article", model.getName());
Assert.assertEquals(2, model.getComments().size());
Assert.assertEquals(1, model.getComments().get(0).getId().longValue());
Assert.assertEquals("some comment", model.getComments().get(0).getText());
Assert.assertEquals(2, model.getComments().get(1).getId().longValue());
Assert.assertEquals("some another comment", model.getComments().get(1).getText());


Although this configuration works for the multiple values of comments, if it changes to a single value, then we need to define a custom TypeAdapter to handle both single and multiple values at once.

Custom TypeAdapter in Gson

In order to achieve a custom JSON deserialization behavior, we need to create a TypeAdapter. This behavior will be responsible for adding any single value to the list as same as multiple values are being added automatically by Gson.

First of all, let's create a Gson TypeAdapter for this purpose:

public class CommentListTypeAdapter extends TypeAdapter<List> {

    @Override
    public void write(JsonWriter out, List list) throws IOException {

    }

    @Override
    public List read(JsonReader in) throws IOException {
        return null;
    }

}


We need to access Gson instance inside our TypeAdapter to preserve and reuse default deserialization behavior for object and collection types.

So, let's define a constructor and fields of Gson instance and some default adapters:

private Gson gson;
private TypeAdapter<CommentModel> objectTypeAdapter;
private TypeAdapter<List<CommentModel>> listTypeAdapter;

public CommentListTypeAdapter(Gson gson) {
    this.gson = gson;
    this.objectTypeAdapter = gson.getAdapter(CommentModel.class);
    this.listTypeAdapter = gson.getAdapter(new TypeToken<List<CommentModel>>() {});
}


Since we need to deserialize only, we can omit the write method of our adapter. However, we can implement in any case by simply delegating it to the default adapters.

Hence, let's implement write method by using listTypeAdapter:

@Override
public void write(JsonWriter out, List list) throws IOException {
    listTypeAdapter.write(out, list);
}


Then, we implement the read method of our adapter to come up with our expected deserialization behavior:

@Override
public List read(JsonReader in) throws IOException {

    List<CommentModel> deserializedObject = new ArrayList<>();

    // type of next token
    JsonToken peek = in.peek();

    // if the json field is single object just add this object to list as an element
    if (JsonToken.BEGIN_OBJECT.equals(peek)) {
        deserializedObject.add(deserializeObject(in));
    }

    // if the json field is array then implement normal array deserialization
    if (JsonToken.BEGIN_ARRAY.equals(peek)) {
        deserializedObject.addAll(deserializeList(in));
    }

    return deserializedObject;
}


TypeAdapterFactory in Gson

Certainly, we should consider using TypeAdapterFactory in Gson. There are two benefits: one of them is we can implement a generic factory method to create different type adapters for different types. The other one is we can access the underlying Gson instance and reuse it, which we are primarily looking for right now.

So, let's create a custom TypeAdapterFactory:

public class CommentListTypeAdapterFactory implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        return (TypeAdapter<T>) new CommentListTypeAdapter(gson);
    }

}


As we notice, we can pass the existing Gson instance to our CommentListTypeAdapter with the help of our custom TypeAdapterFactory .

Registering TypeAdapterFactory Into Gson

Finally, we need to register our custom TypeAdapterFactory into the Gson context to make it work.

Let's update our ArticleModel:

@JsonAdapter(CommentListTypeAdapterFactory.class)
@Expose
private List<CommentModel> comments;


Now, we can consume single value structures and multiple values at the same time:

String responseText = "{
  "id": 1,
  "name": "sample article",
  "comments": {"id":1, "text":"some comment"}
  }";
Gson gson = new Gson();
ArticleModel model = gson.fromJson(responseText, ArticleModel.class);

Assert.assertEquals(1, model.getId().longValue());
Assert.assertEquals("sample article", model.getName());

Assert.assertEquals(1, model.getComments().size());
Assert.assertEquals(1, model.getComments().get(0).getId().longValue());
Assert.assertEquals("some comment", model.getComments().get(0).getText());


Further Improvements With the Power of Generics

We can go further by refactoring our code with the help of generics and reflection in the benefit of reusability. To prevent creating a new adapter for each type it can be essential to follow this method depending on the purpose.

Similarly, we implement another TypeAdapterbut this time, we handle it generically:

public class SingleAwareListTypeAdapter extends TypeAdapter<List> {

    private Gson gson;
    private TypeAdapter<?> objectTypeAdapter;
    private TypeAdapter<List> listTypeAdapter;

    public SingleAwareListTypeAdapter(Gson gson, Class elementClassType) {
        this.gson = gson;
        // we need to carry element's type by passing the element class type to object
        // type adapter otherwise gson deserialize our objects as LinkedTreeSet which we
        // do not expect that.
        this.objectTypeAdapter = gson.getAdapter(elementClassType);
        // list adapter for only serializing do not need the type of element inside list
        // here since all the output will be String at the end.
        this.listTypeAdapter = gson.getAdapter(List.class);
    }

    @Override
    public void write(JsonWriter out, List list) throws IOException {
        // Since we do not serialize our comment list with gson we can omit this part
        // but anyway we can simply implement by reusing gson list type adapter
        listTypeAdapter.write(out, list);
    }

    @Override
    public List read(JsonReader in) throws IOException {
        List deserializedObject = new ArrayList<>();
        // type of next token
        JsonToken peek = in.peek();
        // if the json field is single object just add this object to list as an element
        if (JsonToken.BEGIN_OBJECT.equals(peek)) {
            deserializedObject.add(deserializeObject(in));
        }
        // if the json field is array then deserialize the objects inside
        if (JsonToken.BEGIN_ARRAY.equals(peek)) {
            in.beginArray();
            while (in.hasNext()) {
                if (JsonToken.BEGIN_OBJECT.equals(in.peek())) {
                    deserializedObject.add(deserializeObject(in));
                }
            }
            in.endArray();
        }
        return deserializedObject;
    }

    private Object deserializeObject(JsonReader in) throws IOException {
        // just use gson object type adapter
        return objectTypeAdapter.read(in);
    }
}


As a result, we can use the same adapter for different element types as long as we need the same behavior.

We should also create another TypeAdapterFactory implementation, which will use our new generic SingleAwareListTypeAdapter :

public class SingleAwareListTypeAdapterFactory implements TypeAdapterFactory {

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        // proceed only if the incoming type is a parameterized type
        if (!ParameterizedType.class.isInstance(type.getType())) {
            return null;
        }

        ParameterizedType parameterizedType = ParameterizedType.class.cast(type.getType());
        Type elementType = parameterizedType.getActualTypeArguments()[0];
        Class rawElementType = Class.class.cast(elementType);

        return (TypeAdapter<T>) new SingleAwareListTypeAdapter(gson, rawElementType);
    }

}


It is important to let the Gson know the list element's type information in runtime otherwise our models are deserialized as LinkedTreeSet by default.

Since we need the runtime class of incoming list elements, first, we obtained the ParameterizedType, then the runtime class type of the element inside the collection.

In this way, we pass the type information of the element into the SingleAwareListTypeAdapter as a parameter in the constructor.

Finally, let's change our ArticleModel to use our new generic adapter factory:

@JsonAdapter(SingleAwareListTypeAdapterFactory.class)
@Expose
private List<CommentModel> comments;


As a result, with the help of generics and reflection, we can reuse the same adapter, SingleAwareListTypeAdapterFactory, for the other properties with different element types as well.

Therefore, this will bring us an effective way of reusability.

Conclusion

In this tutorial, we learned how to implement custom adapters to handle variable responses in Gson.

All the source code of examples shown in this tutorial are available over on GitHub. Hope you enjoyed!

Further Reading

Gson Tutorial: How to Serialize and Deserialize Primitive Types

Gson: Deserialization of Generic Types

Topics:
java ,gson

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}