In this blog post, we'll explore how Gson deals with Java generics. Generics are a challenge for any data mapping library, since they bring an uncertainty with them. If you want to review Java generics, use cases and implications, feel free to read up on the Wikipedia article. Gson is fairly well designed to deal with generics, but there are a few things you need to know. Interested? Keep on reading!
Of course, this will not be the only post in our Gson series. If you're interested in the other topics, check out our series outline:
Gson Series Overview
- Mapping of Enums
- Mapping of Circular References
- Generics
- Custom Serialization for Simplification (Part 1)
- Changing the Default Serialization with Custom Serialization (Part 2)
- Custom Deserialization Basics
- Custom Instance Creator
- Customizing (De)Serialization via @JsonAdapter
- Custom Deserialization for Calculated Fields
- On-The-Fly-Parsing With Streams
- ProGuard Configuration
Serialization of Generics
Let's start with the simpler case of serialization. Since we want to transform a Java object into JSON we already know what type and what mapping we need to use, correct?
Well, not completely. But let's get going with the most commonly used generic: Java collections. We've two lists, one containing Integer
values and the other one String
values.
As you've already seen in the previous blog posts, transforming Java objects into JSON is usually a straight-forward gson.toJson()
call:
Gson gson = new Gson();
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<String> stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
String integerJson = gson.toJson(integerList);
String stringJson = gson.toJson(stringList);
This usually works here as well. However, there is no guarantee it does! With plain standard Java types Gson can figure out the type without any problems, but when you transform complex object models, we always recommend going the safe new TypeToken
route:
Gson gson = new Gson();
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
List<String> stringList = new ArrayList<>();
stringList.add("1");
stringList.add("2");
stringList.add("3");
Type integerType = new TypeToken<List<Integer>>() {}.getType();
Type stringType = new TypeToken<List<String>>() {}.getType();
String integerJson = gson.toJson(integerList, integerType);
String stringJson = gson.toJson(stringList, stringType);
The new TypeToken
calls create a type literal by using an empty, anonymous inner class. That's why the line looks a little funky and ends with (){}
.
Nevertheless, this will make sure Gson knows the correct type of the generic and you'll receive a complete JSON:
integerJson = "[1,2,3]"
stringJson = "["1","2","3"]"
Once again, for a lot of simple cases, like the one above, this isn't necessary. We recommend to get used to using the TypeToken
approach just to avoid the eventual issue you'll run into.
Before we look at the deserialization, let's review one more example of a generic outside of Java collections. Our Box
class is very plain and just contains one object of a during compile-time unknown type:
public class Box<T> {
private T boxContent;
public Box(T t) {
this.boxContent = t;
}
}
Our neat Box
class can handle any kind of object type we throw at it. As we've mentioned above, if you want to be on the safe side, use the new TypeToken
method even during serialization.
Gson gson = new Gson();
Box<String> stringBox = new Box<>("String Type");
Box<Integer> integerBox = new Box<>(42);
// the class UserDate is from previous guides (https://futurestud.io/blog/gson-builder-formatting-of-dates-custom-date-time-mapping/)
Box<UserDate> complexBox = new Box<>(new UserDate("Norman", "norman@fs.io", 26, true));
Type stringType = new TypeToken<Box<String>>() {}.getType();
Type integerType = new TypeToken<Box<Integer>>() {}.getType();
Type complexType = new TypeToken<Box<UserDate>>() {}.getType();
String integerJson = gson.toJson(integerBox, integerType);
String stringJson = gson.toJson(stringBox, stringType);
String complexJson = gson.toJson(complexBox, complexType);
This will generate perfect JSON matches for the created Java objects.
We promise, it gets more interesting during the deserialization! We'll dive into the details in the next section.
Deserialization of Generics
Let's assume we're receiving a JSON from our API, which utilizes a Java generic. For example, using our Box
class from the previous section we can expect the following JSON:
{
"boxContent": {
"_name": "Norman",
"age": 26,
"email": "norman@fs.io",
"isDeveloper": true,
"registerDate": "Jun 7, 2016 7:15:29 AM"
}
}
This JSON shows you pretty well what is the core problem.
Gson needs to know what the type of Box
is, otherwise it's impossible to map the JSON. For example, if the JSON would contain a Box<String>
, it's a completely different mapping.
Thus, we need specify the Box
type when deserializing generics. Of course, we use the TypeToken
class for it.
String complexGenericJson = "{\"boxContent\":{\"_name\":\"Norman\",\"age\":26,\"email\":\"norman@fs.io\",\"isDeveloper\":true,\"registerDate\":\"Jun 7, 2016 7:15:29 AM\"}}";
Type complexType = new TypeToken<Box<UserDate>>() {}.getType();
Gson gson = new Gson();
Box boxWithData = gson.fromJson(complexGenericJson, complexType);
Box<UserDate> boxWithoutData = gson.fromJson(complexGenericJson, Box.class);
As long as you can be very specific about the incoming type, even if it's wrapped in some Java generics, Gson should be able to map it.
If you want to read more on the background on how Gson handles Java generics internally, read the official user guide.
There are some edge cases we won't dive into in this blog post. Nevertheless, we'd like to point out one of the most interesting ones: how to map a polymorphic list with Gson.
Outlook
In this blog post, you've learned what Gson can and cannot do regarding Java generics. Generics are wildly used and quite helpful in many applications. It's very beneficial to be aware of the power and limitations of Generics when it comes data mapping.
If you've feedback or a question, let us know in the comments or on twitter @futurestud_io.
Make it rock & enjoy coding!