I’m waist-deep in a Grails application at work and wanted to come up for air to share something that I can’t believe “just worked.” I can’t go into details on the project, so we’re going to have to use a pretty lame example.
Let’s pretend I’m building a Recipe site, and my model consists of two classes: Recipe and User, and each Recipe belongs to one User. It’s all JSON-powered AJAX, and whenever I display a list of Recipes, I want to also display the name of the User.
Now, I could eagerly fetch recipe.user and use the default JSON converter to do a deep conversion:
However, that’d return all of the attributes of the User as part of the JSON packet. I’d never store User.password in plain text, but I’d rather not reveal things like their e-mail address.
Luckily, Grails lets you register custom “marshallers” for any given class that redefine the behavior of “as JSON.” For example, I can create a custom marshaller for Recipe that solves my problem:
That’s fine and dandy, but where should you register this thing? The example I learned from does it in BootStrap, but that smelled a bit like it’d clog things up.
I decided to put my “what would I do without Grails?” hat on, and landed on a simple Spring-based solution:
- Create a “CustomObjectMarshallers” class that simply held a list of “marshallers.” When its register() method is called, it calls register() on each marshaller.
- Within a custom marshaller, register() executes the registration code above.
- In BootStrap, ask Spring for the configured CustomObjectMarshallers instance and tell it to register().
Here’s the code.
Our RecipeMarshaller, in src/groovy/recipe:
Our CustomObjectMarshallers, in src/groovy/util/marshalling:
My Spring bean definitions:
And here’s line of code that makes it all happen in Bootstrap.groovy. (For information on how to get a springContext in BootStrap, see Getting Spring Beans in Grails’ BootStrap.):
Results and Conclusions
It definitely feels a bit like black magic, but this “just worked.” The first time. Seriously. I reran the initial Recipe.list() as JSON, and here’s what I saw:
Now, given that the alteration of the “as JSON” behavior happens very, very far away from the actual controller rendering results, this is definitely something I would recommend documenting before you make your teammates ask “how the hell did that happen?”