Custom deserialization with GSON

Daniil Popov
4 min readJan 24, 2017

JSON is one of the most popular and widely used formats for exchanging and transferring structured data. There are a lot of third-party libraries for parsing and composing JSON-objects. And GSON is one of them. It was written by Google and being used on server, desktop and mobile platforms. I’m sure you’re familiar with this awesome library.

In most cases GSON works as expected just out of box: you declare Java-class and then serialize it to JSON-object and deserialize it back. No annotations, no configs, no inheritance and no additional meta-data required. Just POJO.

But sometimes you have to tell GSON how to serialize or deserialize some kind of “complex” objects. For instance your JSON represents array of geometry shapes such as rectangles, circles, lines etc. Probably JSON-object for the shape has some field describing its type. Shapes have some parameters in common (x and y coordinates) and of course shapes have their own parameters. Circle has radius, but rectangle has width and height. JSON may looks like following:

{
"shapes": [
{
"type": "rect",
"x": 20,
"y": 20,
"width": 200,
"height": 100
}, {
"type": "circle",
"x": 300,
"y": 300,
"radius": 200
}, {
"type": "line",
"x": 0,
"y": 0,
"toX": 600,
"toY": 600
}
]
}

And corresponding Java-class:

class ShapeArray {
Shape[] shapes;
}

But how to parse that?

First solution that comes to mind is to declare Java-class Shape containing all possible fields for all shape types. This is really ugly and misleading because every instance of that class will contains null/empty nonsense fields. Can you imagine rectangle with radius or line with width and height? Furthermore you’ll have to resolve shape type in runtime with switch or if statements. For example to calculate shape’s square. It will be hard to maintain if you want to add new shape type.

There is better solution suggested by Object Oriented Programming. What if we declare base abstract class Shape and concrete implementations such as RectangleShape, CircleShape, LineShape etc. In this case you will be able to separate parameters in each class so rectangle will never have radius. It will looks like following:

abstract class Shape { 
protected int x;
protected int y;
}
class RectangleShape extends Shape {
private int width;
private int height;
}
class CircleShape extends Shape {
private int radius;
}
class LineShape extends Shape {
private int toX;
private int toY;
}

Looks good. But how to tell GSON how to deserialize Shape instance? How will it know which descendant instantiate when parsing corresponding JSON? That’s pretty easy. You can setup custom type adapter:

Gson gson = new GsonBuilder()
.registerTypeAdapter(Shape.class, new ShapeDeserializer())
.create();
class ShapeDeserializer implements JsonDeserializer<Shape> {
@Override
public Shape deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement type = jsonObject.get("type");
if (type != null) {
switch (type.getAsString()) {
case "rect":
return context.deserialize(jsonObject,
RectangleShape.class);
case "circle":
return context.deserialize(jsonObject,
CircleShape.class);
case "line":
return context.deserialize(jsonObject,
LineShape.class);
}
}
return null;
}
}

Be careful when calling deserialize method for Shape descendants. Make sure you do it through context instance. Otherwise there will be infinite call of deserialize method causing stack overflow.

Now you can easily parse JSON with geometric shape array with following code:

ShapeArray parse(final String json) {
return gson.fromJson(json, ShapeArray.class);
}

Example above is about a case when you have to deserialize a complex object. But there are interesting situations when it is necessary to define type adapter for primitive type such as boolean. By default GSON considers Java boolean type is represented with true/false constants in JSON object. Let’s imagine that you have to parse JSON object where boolean values represented with 1/0 or (more terrible way) with “yes”/”no” string constants. If you try to parse such JSON object GSON will fail with JsonSyntaxException.

The best way to solve described problem is to get rid of developer who serialize boolean values to JSON as 1/0 or “yes”/”no”. If it’s not possible for some reasons you can do some magic with GSON and solve this problem on your side. The magic is the same as in the previous problem with Shape parsing: custom type adapter but for Boolean class. Look at the following:

Gson gson = new GsonBuilder()
.registerTypeAdapter(boolean.class, new BooleanDeserializer())
.create();
class BooleanDeserializer implements JsonDeserializer<Boolean> {
@Override
public Boolean deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException {
if (json.isJsonPrimitive()) {
JsonPrimitive primitive = json.getAsJsonPrimitive();
if (primitive.isBoolean()) {
return primitive.getAsBoolean();
}
if (primitive.isNumber()) {
return primitive.getAsInt() == 1;
}
if (primitive.isString()) {
return "yes".equals(primitive.getAsString());
}
}
throw new JsonParseException("Wrong boolean");
}
}

That’s all. With this type adapter you can parse “yes”/”no”, 1/0 and true/false boolean values from JSON.

In this article I’ve tried to show you what powerful, easy-to-use and flexible GSON library is. It can be customized and tweaked as you want so you will be able to parse any crazy JSON object ever exists.

One can add there is another type adapter called JsonSerializer. It means you can serialize Java object in any possible way allowed by JSON specification. It does the the same job as JsonDeserializer but conversely.

Hope you enjoy GSON! Good luck!

--

--