Play Framework 1.2 - Better JSON serialization with FlexJSON

20 April 2011

Erik Bakker

by Erik Bakker

When writing rich interfaces, you often have to generate a JSON representation of your data to be used in your HTML templates. Play framework 1.2 comes bundled with Google’s JSON library Gson, but the developers of this library have made some design choices that keep it from being ideal for generating JSON that is going to be sent to the HTTP client.

Objective

We want to be able to create multiple different JSON views on the same object, to be used in our Play templates. This is useful, because entities are often used in several contexts. A news item may be shown with only a few details in a listing of news items, with more details on a separate page for that news item, and with even more details for an editor of the website.

Problems with Gson

When you use Gson, the default JSON representation of a class is a map with key-value pairs that represent the field names and their values. Serialization is done recursively, which poses the first problem, namely that you cannot serialize objects that have circular references. In a Play application, structures like these are very common. Consider the following two classes:

@Entity
public class Post extends Model {
    @ManyToOne
    public Author author;
    public String title;
    public Integer views;
    public String content;
}
@Entity
public class Author extends Model {
    public String name;
    @OneToMany(mappedBy="author")
    public Set<Post> posts;
}

Trying to serialize an instance of either of them with Gson will fail with the following error:

IllegalStateException occured : circular reference error Offending field: author Offending object: com.google.gson.ObjectTypePair@1

What happens is that during serialization of a post, the author is recursively serialized, which holds a reference to the post, thereby causing a circular reference which would lead to infinitely deep JSON trees.

There are more problems with Gson. By default, Gson serializes all fields of an object, except transient fields. Additional fields can be excluded by implementing your own ExclusionStrategy. This include-by-default behaviour seems perfectly reasonable when you use the JSON representation to transfer an object between multiple systems. We however want to use the JSON to show parts of an object to clients. For our purpose, it is much more practical to explicitly include fields than to explicitly exclude them. For example, suppose that we add a password field to our Author class. If fields are included by default, the password would be visible in JSON output if we forget to also explicitly exclude it in the ExclusionStrategy. On the other hand, if we would use a system where we had to explicitly include fields, no harm would come to us if we added a password field to the Author class.

Gson can be switched into a mode where it excludes fields by default, and includes only fields that are annotated with @Expose. This seems nice at first, but this couples the JSON representation definition with the model itself, which conflicts with our objectives. After all, we wanted to have multiple different JSON views on an object. The ‘content’ of a post should be included on the details page, but not on the listing page. In other words, we want the definition of the JSON view to be part of the serializer, of which we can have many, instead of being part of the class we are serializing.

Even this is possible with Gson, for we can create an ExclusionStrategy that excludes by default, and includes only explicitly named fields. We can also have multiple exclusion strategies, so we can satisfy our objective. However, writing an ExclusionStrategy that works well with nested objects is extremely cumbersome.

Taking it all together, the circular reference issues, the including by default and the cumbersome exclusion strategies make Gson not ideal for generating JSON to use in a template.

FlexJSON

With FlexJSON, it is very easy to specify which fields you want to serialize, even with nested objects. Take for example this serializer for a details view of a post:

JSONSerializer postDetailsSerializer = new JSONSerializer().include(
                "title",
                "content",
                "author.name").exclude("*");

This serializer includes the title and content of the post object, and also the name of the author. As you can see, you can specify which fields of nested objects to include simply by adding a dot to the field followed by the field of the nested object. To serialize a post object, we can now use

postDetailsSerializer.serialize(post);

To have a JSON view of a post without the content, we can simply create another serializer:

JSONSerializer postListSerializer = new JSONSerializer().include(
                "title",
                "author.name").exclude("*");

To use FlexJSON in your Play application, download the latest version from http://flexjson.sourceforge.net/ and put the jar into the lib folder in your Play application.

FlexJSON may not suit all your needs, but will help you out in a whole lot of cases. Remember, you don’t have to fully switch to FlexJSON to take advantage of it, you can still use other JSON libraries might the need arise in a given situation.

Convenience classes

We can easily use these serializers from our view templates, if we create a custom Java object extension:

/**
 * Extensions for the template system.
 */
public class SerializerExtensions extends JavaExtensions {
    /**
     * Serialize a model to JSON with a given serializer.
     */
    public static String serializeWith(Object model, String serializer) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
        JSONSerializer js = (JSONSerializer) Serializers.class.getField(serializer).get(null);
        return js.serialize(model);
    }

    /**
     * Serialize a list of models to JSON with a given serializer.
     */
    public static String serializeWith(Collection<Object> models, String serializer) throws IllegalArgumentException, SecurityException, IllegalAccessException, NoSuchFieldException {
        JSONSerializer js = (JSONSerializer) Serializers.class.getField(serializer).get(null);
        return js.serialize(models);
    }
}

With this code in place, we can easily convert a single model or a collection of models into JSON, while in a Play template:

<script type="text/javascript">
  var post = ${post.serializeWith('postDetailsSerializer').raw()};
</script>

This works equally for a collection of posts:

<script type="text/javascript">
  var posts = ${posts.serializeWith('postListSerializer').raw()};
</script>

The Java extension gets the relevant serializers from the Serializers class, which holds static references:

public class Serializers {
    public static final JSONSerializer postListSerializer;
    public static final JSONSerializer postDetailsSerializer;

    static {
        boolean prettyPrint = Play.mode == Mode.DEV;

        postListSerializer = new JSONSerializer().include(
                "title",
                "author.name").exclude("*").prettyPrint(prettyPrint);

        postDetailsSerializer = new JSONSerializer().include(
                "title",
                "author.name",
                "content").exclude("*").prettyPrint(prettyPrint);
    }
}

In development mode, we get pretty printed JSON, while in production mode the JSON is more compact.

Notes

You might want to instantiate Serializers differently to the approach used in this example. For example, you can choose to store them as fields of the class they are supposed to serialize. Be aware of multithreading issues though. A serializer is thread-safe, but only when you do not call the include or exclude methods. If you do have to call those from your request handling threads, be very careful. Multithreading problems might show up only in production mode, since by default only a single thread is used in development mode.

Conclusion

The design choices made for GSON do not make it very suitable for generating JSON that is going to be used in your application templates. With FlexJSON, it is much easier to create multiple views on the same data, which is extremely useful in web applications. With custom Java extensions, FlexJSON serialization can be used seamlessly from your templates.