So far I’ve shown how to produce JSON from simple collections and how to convert domain objects to JSON, in this final post in my three-part look at rendering JSON from Grails controllers, we’ll take a look at customising the way objects are rendered by the JSON converter.

What’s Wrong with the Default Converter?

The default behaviour of the JSON converter is to convert all properties of a POGO and all persisted properties of a domain object into propertyName: propertyValue JSON pairs. While it’s very useful, there are some common problems with the default conversions.

Am I Exposing Too Much?

In most situations you will not need to make all of an object’s properties available in a public API and cutting down on the number of properties means an increase in performance as you’re converting and transferring less data. There may also security issues with making your class names public or giving out domain-class ids in some applications, also while it might be fine to have all the properties of a bean public now, if in the future you add a sensitive property to a domain class can you be confident you’ll check that it’s not being included in your public API?

A Custom JSON Converter

In many applications I’ve worked on, the solution to the exposure problem is to develop a layer of adapters which wrap domain objects so only certain properties are exposed via an API. While this approach works, it can become a big development and maintenance overhead.

Thankfully, Grails provides a standard way to extend and modify JSON converters by using the ObjectMarshaller interface, below is a simple example for overriding the rendering of the “Person” class (from my previous posts) by registering a conversion closure in Bootstap.groovy:

import grails.converters.JSON
    class BootStrap {
        def init = {servletContext ->
            JSON.registerObjectMarshaller(Person) {
                def returnArray = [:]
                returnArray['name'] = it.name
                returnArray['addrs'] = it.addresses
                return returnArray
        }
}

Now rendering a Person object only shows the addresses and a name (and no class or id on the Person):

{
    "name": "David Bower",
    "addrs": [
        {
            "class": "Address",
            "id": 6,
            "person": {
                "class": "Person",
                "id": 7
            },
            "building": "25",
            "street": "High Street",
            "country": "UK"
        }
    ]
}

Notice how the Address still shows the Person class and id – it’s because it’s still using the defaults. We can override the Address class in a similar way:

        JSON.registerObjectMarshaller(Address) {
            def returnArray = [:]
            returnArray['building'] = it.building
            returnArray['street'] = it.street
            returnArray['city'] = it.city
            returnArray['country'] = it.country
            return returnArray
        }

A Generic Approach to Custom Conversions

You may decide that the default converter would be fine if only it did or didn’t do something. If so it’s easily remedied by an ObjectMarshaller and a quick look into the Grails source. Let’s assume we like the default conversion of POGOs except that we really don’t want to render the “class” property…

Better Living Through Copying Customising

In the Groovy source code the default JSON converters are defined in org.codehaus.groovy.grails.web.converters.marshaller.json. Here’s the source of the GroovyBeanMarshaller, used for non-domain Groovy objects:

/*
 * Copyright 2004-2008 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.codehaus.groovy.grails.web.converters.marshaller.json;

import grails.converters.JSON;
import groovy.lang.GroovyObject;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.codehaus.groovy.grails.web.converters.exceptions.ConverterException;
import org.codehaus.groovy.grails.web.converters.marshaller.ObjectMarshaller;
import org.codehaus.groovy.grails.web.json.JSONWriter;
import org.springframework.beans.BeanUtils;

public class GroovyBeanMarshaller implements ObjectMarshaller {

    public boolean supports(Object object) {
        return object instanceof GroovyObject;
    }

    public void marshalObject(Object o, JSON json)
        throws ConverterException {
        JSONWriter writer = json.getWriter();
        try {
            writer.object();
            PropertyDescriptor[] properties =
                BeanUtils.getPropertyDescriptors(o.getClass());
            for (PropertyDescriptor property : properties) {
                String name = property.getName();
                Method readMethod = property.getReadMethod();
                if (readMethod != null
                    && !(name.equals("metaClass"))) {
                    Object value =
                        readMethod.invoke(o, (Object[]) null);
                    writer.key(name);
                    json.convertAnother(value);
                }
            }
            Field[] fields = o.getClass().getDeclaredFields();
            for (Field field : fields) {
                int modifiers = field.getModifiers();
                if (Modifier.isPublic(modifiers)
                    && !(Modifier.isStatic(modifiers)
                        || Modifier.isTransient(modifiers))) {
                    writer.key(field.getName());
                    json.convertAnother(field.get(o));
                }
            }
            writer.endObject();
        } catch (ConverterException ce) {
            throw ce;
        } catch (Exception e) {
            throw new ConverterException(
                "Error converting Bean with class "
                + o.getClass().getName(), e);
        }
    }

Looking at the code we can easily stop the class name being rendered by adding another test to the first if statement:

if (readMethod != null
    && !name.equals("metaClass")
    && !name.equals("class")) {

By creating a new class, “NoClassNameObjectMarshaller”, we can then register our new marshaller in Bootstrap.groovy:

    JSON.registerObjectMarshaller(new NoClassNameObjectMarshaller())

Priorities

If you were to use the NoClassNameObjectMarshaller in practice you would immediately notice that domain class rendering is broken! The problem is that we’ve registered our NoClassNameObjectMarshaller as handling all Groovy objects in the “supports” method so it also overrides the existing “DomainClassMarshaller”. This can be solved by re-prioritising the renderers in Bootstrap.groovy so the DomainClassMarshaller is checked for support first :

import grails.converters.JSON
    class BootStrap {
        def init = {servletContext ->
            JSON.registerObjectMarshaller(
                new NoClassNameObjectMarshaller(), 1)
            JSON.registerObjectMarshaller(
                new DomainClassMarshaller(true), 2)
        }
}

Notice that the higher-valued priorities are checked first (and if you don’t specify a priority it will be set to zero).

JSON and deep.JSON Confusion

In part 2 of the series I pointed out the difference between the standard JSONConverter and the deep.JSONConverter and it is important to note that if you register an object marshaller for one of the converters, it will not be picked up by the other. In other words if you want to override both the standard and deep converters you must explicitly do so in Bootstrap.groovy.

Other Grails Marshallers

The examples above give a feel for how easy it is to customise the default JSON rendering behaviour and the Grails source provides many more marshallers which I encourage you to take a look at for examples on rendering Enums, Dates, Java beans and other types.

Rendering transient fields for all domain objects could be made possible by combining the features of the DomainClassMarshaller and the GroovyBeanMarshaller, however, I’ve found it more useful to override behaviour on a class-by-class approach since the defaults are generally more friendly.

I recommend caution when overriding the rendering of all POGOs (as demonstrated above) and it may be wise to limit the support to certain packages or classes to avoid unexpected problems when rendering standard types.

All Good Things

I hope that this short series has given some insight into the practicalities of rendering JSON in real applications. Out of all the examples provided, the basic rendering of collections and registering simple custom marshallers in closures are the two techniques I use most often and find most useful. I’m still waiting to find a good reason to use a JSONBuilder…


5 Responses to “Rendering JSON in Grails: Part 3 Customise your JSON with Object Marshallers”

  1. B Sreekanth Says:

    Thanks you very much for your time explaining the concept. it has been really useful. Going through your other posts. One qn, ObjectMarshaller approach worked great to filter out needed attributes, but any way this to override/disable in the controller. Suppose I need one more attribute from the object. Then custom mappping is the only way? I posted a qn before I see your page at http://stackoverflow.com/questions/2562684/rendering-json-in-grails-with-part-of-the-attributes-of-an-object . Can you help me to show how to get custom attributes from the object, and get in the JSON format? thanks again.


  2. Have you ever had the problem with “Misplaced Key” when using converters.deep.JSON ?

    I don’t know how to solve it!

    I want to render an AdSpaceBooking object that has a User, an AdSpace and a Date. When the rendering comes to a list of Authorities in the user it fails with a Misplaced Key exception.


    • Just found a work around. In grails-app/Config.groovy set:

      grails.converters.json.circular.reference.behaviour = “INSERT_NULL”

      The default behaviour is circular referencing, which gives the exception. It is not really necessary either..


Leave a Reply