Gson Advanced — Customizing (De)Serialization via @JsonAdapter

In this blog post, we'll show how you can simplify the customization of (de)serialization. In the past few blog posts, we've demonstrated how to customize serialization, deserialization and how to utilize instance creators.

All of those options were only available via a custom Gson instance and some boilerplate code. Gson 2.7 introduced a simple annotation were you can save quite a bit of code and achieve the same result. If you're interested in @JsonAdapter, keep 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

@JsonAdapter Annotation

If you've read our previous blog posts on custom (de)serialization, you know the general structure is not too complicated, but always a few lines of boilerplate code:

GsonBuilder gsonBuilder = new GsonBuilder();

Type merchantListType = new TypeToken<List<Merchant>>() {}.getType();  
JsonSerializer<List<Merchant>> serializer = ...; // implementation detail  
gsonBuilder.registerTypeAdapter(merchantListType, serializer);

Gson customGson = gsonBuilder.create();  
String customJSON = customGson.toJson(subscription);  

The snippet above shows you an example of a custom serializer. We'll still need an implementation of the JsonSerializer interface, but the other code around it can be simplified.

Custom Serialization

The first step is to pull out the serializer object in the snippet above and move it into a class. The class has the identical setup as the anonymous declaration as we've done it as an object:

public class MerchantListSerializer implements JsonSerializer<List<Merchant>> {  
    @Override
    public JsonElement serialize(List<Merchant> src, Type typeOfSrc, JsonSerializationContext context) {
        JsonArray jsonMerchant = new JsonArray();

        for (Merchant merchant : src) {
            jsonMerchant.add("" + merchant.getId());
        }

        return jsonMerchant;
    }
}

Once you've wrapped it in a public class, you can use it in the @JsonAdapter annotation. The @JsonAdapterannotation, just like the other annotations we've explored in previous posts, are added to the Java model. In the example above, we would need to change the models that include List<Merchant> properties.

For example:

public class UserSubscriptionAnnotation {  
    String name;
    String email;
    int age;
    boolean isDeveloper;

    // new!
    @JsonAdapter(MerchantListSerializer.class)
    List<Merchant> merchantList;
}

The property that requires a custom serializer, will be enhanced by the @JsonAdapter annotation. The annotation only accepts one single parameter: a class reference. That class needs to implement either JsonSerializer or JsonDeserializer.

All the following Gson conversions will utilize your custom (de)serialization. You won't need a custom Gson instance anymore. Thus, the following snippet would be sufficient:

UserSubscriptionAnnotation subscription = new UserSubscriptionAnnotation(  
                "Norman",
                "norman@fs.io",
                26,
                true,
                subscribedMerchants);

Gson gson = new Gson();  
String fullJSON = gson.toJson(subscription);  

You can completely get rid of the GsonBuilder part and simply use the default new Gson() instance.

Custom Deserialization

The same approach and annotation also works for custom deserialization. If you check the code from our custom deserialization blog post you'll find the following setup code:

GsonBuilder gsonBuilder = new GsonBuilder();

JsonDeserializer<UserDate> deserializer = ...; // implementation detail  
gsonBuilder.registerTypeAdapter(UserDate.class, deserializer);

Gson customGson = gsonBuilder.create();  
UserDate customObject = customGson.fromJson(userJson, UserDate.class);  

Once again, the alternative solution is to change it from a custom Gson instance with registered type adapters to annotating the model.

First, we've to extract the deserializer into a class:

public class UserDateDeserializer implements JsonDeserializer<UserDate> {  
    @Override
    public UserDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject = json.getAsJsonObject();

        Date date = new Date(
            jsonObject.get("year").getAsInt(),
            jsonObject.get("month").getAsInt(),
            jsonObject.get("day").getAsInt()
        );

        return new UserDate(
            jsonObject.get("name").getAsString(),
            jsonObject.get("email").getAsString(),
            jsonObject.get("isDeveloper").getAsBoolean(),
            jsonObject.get("age").getAsInt(),
            date
        );
    }
}

As you can see, the code in the deserialize() stays identical.

The second step is to add the annotation to the model:

@JsonAdapter(UserDateDeserializer.class)
public class UserDate {  
    private String _name;
    private String email;
    private boolean isDeveloper;
    private int age;

    private Date registerDate 
}

A slight difference to the previous example of custom serialization via annotation is that we're annotating the entire class and not a specific property. Both ways are possible and useful!

The deserialization is now reduced to a single line of code:

UserDate standardObject = new Gson().fromJson(userJson, UserDate.class);  

Beyond the Scope of @JsonAdapter: Multiple Customizations

One limitation of Java annotations is that you can only add one annotation for each class (or property). If one of your models or properties requires a custom serializer and a custom deserializer, you'll need to continue to use the long way via a custom Gson instance with registerTypeAdapter() calls.

Outlook

In this tutorial, you've learned how you can use the @JsonAdapter annotation to save a lot of boilerplate code and simplify your code base. While it doesn't add any new functionality, it makes your code cleaner and easier to understand.

Additionally, it prevents you from accidentally registering multiple custom (de)serializer by only allowing one annotation for each model (and property).

If you've feedback or a question, let us know in the comments or on twitter @futurestud_io.

Make it rock & enjoy coding!

Explore the Library

Find interesting tutorials and solutions for your problems.