Gson: Deserialization of Generic Types
JSON, Gson, and Java: take a look at this handy tutorial on deserialization methods in Gson.
Join the DZone community and get the full member experience.
Join For FreeLet's imagine that we are developing a wrapper library for an API, which returns JSON documents as a response. And there are many endpoints that have similar structures, except some fragment and may have a different structure.
Let's go ahead with a simple example. This is what some personal data could look like:
{
"status": "success",
"message": "Data was retrieved",
"data": {
"name" : "John",
"surname": "Smith"
}
}
And here is an example for some department data:
{
"status": "success",
"message": "Data was retrieved",
"data": {
"name" : "Agriculture",
"peopleCount" : 42
}
}
As we can see, only the "data" fragment has a different structure, while the general structure of both documents is the same.
It is obvious that it is better to reuse this general structure and for each document to provide only the remaining part—the "data" subdocument structure. One of the possible solutions could look like:
public class FooResponse<T> {
private final String status;
private final String message;
private final T data;
// constructor, getters
}
...
public class Person {
private final String name;
private final String surname;
// constructor, getters
}
...
public class Department {
private final String name;
private final int peopleCount;
// constructor, getters
}
If we dealt with a class without generics, our code deserializing the string response would look like:
FooResponse<Person> person = gson.fromJson(rawPerson, FooResponse.class);
But in our case it will fail to deserialize the data field, because Gson simply does not know which parameter type to use.
How can we tell Gson about it? There is an alternative signature of the deserialization method—fromJson(String json, Type typeOfT) where Type is java.lang.reflect. Type which may contain information about parametrization too.
One of the ways to extract the type from parametrized class is to use Gson's TypeToken class, which uses reflection magic in order to do this. Our code now will look like this:
FooResponse<Person> person = gson.fromJson(rawPerson, new TypeToken<FooResponse<Person>>(){}.getType())
As we can see, we create an anonymous subclass of TypeToken in this way, and we make its parametrization accessible (for more details, please refer to TypeToken implementation code, it might be an interesting way to learn something new about Java reflection).
This solution works fine, but if you develop a library that will be used by other developers, you might want to hide all Gson usage details inside your implementation code, so using TypeToken as a parameter might be not an option.
Fortunately, it is not a hard task to compose a Type instance with Java's standard library. All we have to do is make our implementation of ParametrizedType interface and make sure it returns the correct type argument. And we can create a function which would return such Type instance.
Type getType(Class<?> rawClass, Class<?> parameter) {
return new ParametrizedType() {
@Override
public Type[] getActualTypeArguments() {
return new Type[] {parameter};
}
@Override
public Type getRawType() {
return rawClass;
}
@Override
public Type getOwnerType() {
return null;
}
}
}
That's it. Now we can use Gson this way:
FooResponse<Person> person = gson.fromJson(rawResponse, getType(FooResponse.class, Person.class))
And our library can have a method that is easy to use by other developer teams:
public <T> FooResponse<T> getResponse(/* parameters for retrieval ,*/ final Class<T> dataClass) {
final String rawResponse = getRawResponse(); // ... e.g. via some http client library
return gson.fromJson(rawResponse, getType(FooResponse.class, dataClass));
}
Here is a GitHub repository with an example illustrated described approach:
https://github.com/AntonBerezin/gson-deserialization-example
Opinions expressed by DZone contributors are their own.
Comments