Marcin Erdmann

Groovy, Grails, Geb...

Excluding fields and properties from marshalling using annotations in Grails

Recently the flexible and configurable marshalling system of Grails was mentioned on Twitter by Graeme Rocher as needing some more documentation. That was probably the outcome of the interesting blog post by J-Wicz on the matter. Rob Fletcher has also blogged about it quite some time ago.

The issue got my attention because in our project I needed to exclude some fields from marshalling on a DTO. The reason for that might be various, for me it was a fact that the DTO contained some intermediate data which was then used to determine a value that is being send to the frontend in a JSON format. One of the fields had to also be formatted so I created a getter for it but the original value was still marshalled which was not desired – you’re not using it then don’t send it to frontend, less data means better performance. For others it might be the fact that a marshalled object contains a big data structure which is not needed in the frontend.

I wanted the solution to be reusable so that it could be applied to multiple classes without the need to create a new marshaller for every one of them. I decided to mark the fields with a @DoNotMarshall annotation which would be then detected in my marshaller and the fields and properties annotated with it won’t be marshalled. I used the information from the abovementioned blog posts as the starting point.

So the first thing is the two annotations – one from marking the fields and accessors to be skipped from marshalling and the second to mark the class on which we want to configure the marshalling. I will explain why the second is needed later on.

The code for DoNotMarshall annotation:

package nl.jworks.marshalling

import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.FIELD, ElementType.METHOD])
@interface DoNotMarshall {
}

And for the ConfigureMarshalling annotation:

package nl.jworks.marshalling

import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target
import java.lang.annotation.ElementType

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface ConfigureMarshalling {
}

Next we’ll need a test for our new marshaller:

package nl.jworks.marshalling

import grails.test.*
import grails.converters.JSON
import org.codehaus.groovy.grails.web.json.JSONObject

class AnnotationDrivenObjectMarshallerTests extends GrailsUnitTestCase {
    protected void setUp() {
        super.setUp()
        JSON.registerObjectMarshaller(new AnnotationDrivenObjectMarshaller<JSON>())
    }

    private JSONObject marshall(object) {
        JSON.parse(new JSON(object).toString())
    }

    void testNonAnnotatedClass() {
        JSONObject marshalled = marshall(new NonAnnotatedClass())
        marshalled.with {
            assert field == 'field'
            assert property == 'property'
        }
    }

    void testAnnotatedClass() {
        JSONObject marshalled = marshall(new AnnotatedClass())
        marshalled.with {
            assert !has('nonMarshalledField')
            assert !has('nonMarshalledProperty')
            assert marshalledField == 'marshalled'
            assert marshalledProperty == 'marshalled'
        }
    }

}

@ConfigureMarshalling
class AnnotatedClass {
    String marshalledField = "marshalled"
    @DoNotMarshall
    String nonMarshalledField = "non marshalled"

    String getMarshalledProperty() { "marshalled" }

    @DoNotMarshall
    String getNonMarshalledProperty() { "non marshalled" }
}

class NonAnnotatedClass {
    @DoNotMarshall
    String field = "field"

    @DoNotMarshall
    String getProperty() { "property" }
}

Few things to note about the test class:

  • in setUp() we are registering our marshaller using JSON#registerObjectMarshaller(ObjectMarshaller<JSON>) method – you will also need to do the same in the bootstrap if you want to use the marshaller in a Grails application
  • AnnotatedClass and NonAnnotatedClass are our test classes for which we define which fields should be skipped when marshalling
  • there are two tests – one for testing if the fields and properties marked with @DoNotMarshall are not marshalled and one for testing if the @DoNotMarshall is ignored if the class itself wasn’t marked with @ConfigureMarshalling

And finally the marshaller implementation:

package nl.jworks.marshalling

import org.codehaus.groovy.grails.web.converters.Converter
import org.codehaus.groovy.grails.web.converters.marshaller.ClosureOjectMarshaller
import org.springframework.beans.BeanUtils
import java.beans.PropertyDescriptor
import java.lang.reflect.Field

class AnnotationDrivenObjectMarshaller<T extends Converter> extends ClosureOjectMarshaller<T> {
    private final static Closure marshallingClosure = { marshalled ->
        Class marshalledClass = marshalled.getClass()
        Field[] fields = marshalledClass.declaredFields
        PropertyDescriptor[] properties = BeanUtils.getPropertyDescriptors(marshalledClass)
        def propertiesToBeMarshalled = properties.findAll { PropertyDescriptor property ->
            List accessorAndField = [property.readMethod, fields.find { it.name == property.name}]
            boolean isAnnotationPresent = accessorAndField.any { it?.isAnnotationPresent(DoNotMarshall) }
            property.name != 'metaClass' && !isAnnotationPresent
        }
        return propertiesToBeMarshalled.inject([:]) { Map result, PropertyDescriptor property ->
            result[property.name] = property.readMethod.invoke(marshalled, null as Object[])
            result
        }
    }

    AnnotationDrivenObjectMarshaller() {
        super(GroovyObject, marshallingClosure.clone())
    }

    @Override
    boolean supports(Object object) {
        super.supports(object) && object.class.isAnnotationPresent(ConfigureMarshalling)
    }
}

I decided to extend from ClosureOjectMarshaller (yes, it’s an ‘Oject’ marshaller, apparently a typo) which allows to specify a closure which is called with the marshalled object as the parameter and shall return another object (in my case a map) that will be marshalled. Thanks to that I don’t have to worry about any JSON building. I’m just returning a map from the closure where keys are property names and values are the respective property values and the result is later on JSONified for me.

The code inside the closure is a modified version of the code from GroovyBeanMarshaller with the sole difference in not marshalling the fields and properties on which the DoNotMarshall annotation is detected.

Finally note that I’ve overridden the supports method – thanks to that the marshaller will be used for any class that bears the ConfigureMarshalling and is a GroovyObject but for non annotated classes the original (in most cases the GroovyBeanMarshaller) marshaller will be used. As the AnnotationDrivenObjectMarshaller creates a map which is then marshalled it has the performance penalty of that extra step so if all the fields are to be marshalled it’s better to let the framework use the original marshaller.

To use this marshaller you need to drop it together with the annotations in the src/groovy directory of your Grails application and register it in the bootstrap as previously mentioned in the description of the test class. As the bootstrap is not available in test classes it might be a good idea to register marshallers in a static method of some utility class. You can then call it both from the bootstap as well as from the unit tests – this way you can easily replicate the marshalling configuration of a running application in the tests.

Happy annotating and marshalling!