Most Grails XML/JSON marshalling discussions you find on the web (as of July 2011) show mainly simple examples, and therefore most code ends up in BootStrap.groovy. Which is perfect if you just want to tweak the XML marshalling a little bit.
We had the task to migrate a Spring MVC application that used a lot of Spring OXM to Grails. We had a lot of custom XStream converters that needed to be ported, and we wanted to switch to Grails internal marshalling. There are really nice XML/JSON marshalling features in Grails and with this post we want to expose them to the reader.
quoting Luke Daley (Jan 12th 2011)
It turns out that there is a completely undocumented set of features built-in to the converter mechanism that will let you do what you want. You are going to have to read through the source though and work it out.
Most of the great features seem to be implemented by Siegfried Puchbauer around Grails 1.1
This blog post focuses on custom XML marshallers, but Grails XML and JSON converters have very similar implementation patterns.
Different kinds of marshalling
There are many alternatives how to register a custom marshaller in Grails.
You can have it really nice and simple with a closure in BootStrap.groovy like described in JSON Rendering Your Classes (12 Aug 2009 by Rob Fletcher)
JSON.registerObjectMarshaller(DateTime) {
return it?.toString("yyyy-MM-dd'T'HH:mm:ss'Z'")
}
You can define your object marshaller right in BootStrap.groovy like replied by Siegfried Puchbauer in this Thread (29th of May 2009)
XML.registerObjectMarshaller Pogo, { pogo, xml ->
xml.attribute 'id', pogo.id
xml.build {
name(pogo.name)
}
}
It’s possible to use a Map to select which field is exposed in JSON/XML as shown in this post Rendering JSON in Grails: Part 3 Customise your JSON with Object Marshallers (February 15, 2010 by David Bower)
JSON.registerObjectMarshaller(Person) {
def returnArray = [:]
returnArray['name'] = it.name
returnArray['addrs'] = it.addresses
return returnArray
}
Or you can implement your own ObjectMarshaller classes like described in this post. One reason to make the extra step and implement custom ObjectMarshallers is that it allows for better unit testing, especially with the new upcoming testing improvments in Grails 1.4.0.M1 and 2.0.0.M1
Beside of better unit test support we also required different marshalling for different situations
- publicApi – For our public API we did not want to expose to much sensitive information like workflow and internal owners. This is our “publicApi” configuration
- contributorApi – For registered content contributors we wanted to expose more, but also reduce the number of Ajax calls, so the user interface can be responsive
- default Grails behavior – We wanted to preserve the default DomainClassMarshaller so similar to dynamic HTML scaffolding we would also have a way to always expose a domain object as XML and JSON.
To fulfill this requirement we can use the XML.createNamedConfig() method in BootStrap.groovy
XML.createNamedConfig('publicApi') {
it.registerObjectMarshaller(new FooPublicMarshaller())
}
XML.createNamedConfig('contributorApi') {
it.registerObjectMarshaller(new FooContributorMarshaller())
}
and then call them in our controllers be using the XML.use() method. So a simplified controller might look like this:
class FooMarshallController {
def show = {
Foo foo = Foo.get(params.id)
withFormat {
// ...
xml {
XML.use('publicApi') {
render foo as XML
}
}
}
}
// Spring-Security annotation like: @Secured(['IS_AUTHENTICATED_REMEMBERED'])
def showContributor = {
Foo foo = Foo.get(params.id)
withFormat {
// ...
xml {
XML.use('contributorApi') {
render foo as XML
}
}
}
}
// Spring-Security annotation like: @Secured(['ROLE_ADMIN'])
def showAdmin = {
Foo foo = Foo.get(params.id)
withFormat {
// ...
xml {
render foo as XML
}
}
}
// ... other actions
}
ConverterConfiguration in BootStrap.groovy
It’s possible to configure further setting in BootSptrap.groovy. As a note: if we want to inherit all the nice default marshallers that come with Grails we must pass a delegate into the constructor of DefaultConverterConfiguration. The delegate will be of type ChainedConverterConfiguration and seems to be a performance optimized immutable implementation.
For instance to turn on XML pretty printing we could add configuration like this:
DefaultConverterConfiguration converterConfiguration = new DefaultConverterConfiguration<XML>(ConvertersConfigurationHolder.getConverterConfiguration(XML)) converterConfiguration.setPrettyPrint true ConvertersConfigurationHolder.setDefaultConfiguration XML, converterConfiguration
Domain Model
To demonstrate the inner workings of marshalObject() we will use a very simple domain model like this:
class Foo {
String name
Integer age
Set bars
static hasMany = [bars:Bar]
}
class Bar {
String name
static belongsTo = Foo
}
// in BootStrap.groovy we add something like
def firstBar = Bar.findOrSaveWhere(name: 'firstBar') //Grails 2.0.0.M1
def secondBar = Bar.findOrSaveWhere(name: 'secondBar') //Grails 2.0.0.M1
def foo= Foo.findOrCreateWhere(name: 'someName', age: 99) //Grails 2.0.0.M1
foo.addToBars(firstBar)
foo.addToBars(secondBar)
foo.save()
The marshalObject() method
Lets start with a very simple marshaller where we decide that we only want to expose the name. No database id, no age and no Bar instances.
class FooContributorMarshaller implements ObjectMarshaller {
public boolean supports(Object object) {
return object instanceof Foo
}
public void marshalObject(Object object, XML converter) {
Foo pogo = object as Foo
converter.chars pogo.name
}
}
output:
<foo>someName</foo>
NameAwareMarshaller interface
If we like to control the element/tag name from within our custom marshaller we can implement the NameAwareMarshaller interface. This will be more useful later once we use the convertAnother() method
class FooContributorMarshaller implements ObjectMarshaller, NameAwareMarshaller {
public boolean supports(Object object) {
return object instanceof Foo
}
public void marshalObject(Object object, XML converter) {
Foo pogo = object as Foo
converter.chars pogo.name
}
String getElementName(Object o) {
return 'specialTagName'
}
}
output:
<specialTagName>someName</specialTagName>
startNode(), attribute(), chars(), end()
We can add arbitrary elements and attributes by using some of the methods from the XML converter class
public void marshalObject(Object object, XML converter) {
Foo foo = object as Foo
converter.attribute 'id', foo.id.toString()
converter.attribute 'theAge', foo.age.toString()
converter.startNode 'name'
converter.chars foo.name
converter.end()
converter.startNode 'guiInstructions'
converter.chars 'someGuiInstructions'
converter.end()
}
output:
theAge="99"> <name>someName</name> <guiInstructions>someGuiInstructions</guiInstructions> </foo>
builder
There is also a small XML builder embeded in grails.converters.XML
public void marshalObject(Object object, XML converter) {
Foo foo = object as Foo
converter.build {
someElement someAttribute: foo.age, foo.name
}
}
output:
<foo> <someElement someAttribute="99">someName </foo>
converter.convertAnother collection
Grails can handle the marshalling of typical types like List, Set and Map
public void marshalObject(Object object, XML converter) {
Foo foo = object as Foo
converter.startNode 'name'
converter.chars foo.name
converter.end()
Map map = [firstKey: 'firstValue', secondKey: 'secondValue']
converter.startNode 'locallyDefinedMap'
converter.convertAnother map
converter.end()
}
output:
<foo>
<name>someName</name>
<locallyDefinedMap>
<entry key="firstKey">firstValue</entry>
<entry key="secondKey">secondValue</entry>
</locallyDefinedMap>
</foo>
converter.convertAnother domain relationships
Grails can handle the marshalling of domain relationships by itself
public void marshalObject(Object object, XML converter) {
Foo foo = object as Foo
converter.startNode 'name'
converter.chars foo.name
converter.end()
if (foo.bars) {
converter.startNode 'bars'
converter.convertAnother foo.bars
converter.end()
}
}
output:
<foo>
<name>someName</name>
<bars>
<bar id="2">
<name>secondBar</name>
</bar>
<bar id="1">
<name>firstBar</name>
</bar>
</bars>
</foo>
converter.convertAnother domain relationships (custom)
But we can also customize the way how we want to marshall domain relationships. Instead of line 12 with the delegation to converter.convertAnother(), we could also create custom markup. For instance if we don’t want to expose database ids or other fields. Or if we want to expose more, like public labels so the client can consume the XML easier.
public void marshalObject(Object object, XML converter) {
Foo foo = object as Foo
converter.startNode 'name'
converter.chars foo.name
converter.end()
if (foo.bars) {
converter.startNode 'setOfBars'
foo.bars.each {
converter.startNode 'customElementNameOfBar'
converter.convertAnother it
converter.end()
}
converter.end()
}
}
output:
<foo>
<name>someName</name>
<setOfBars>
<customElementNameOfBar id="2">
<name>secondBar</name>
<!--<span class="hiddenSpellError" pre=""-->customElementNameOfBar>
<customElementNameOfBar id="1">
<name>firstBar</name>
</customElementNameOfBar>
</setOfBars>
</foo>
New Grails 1.4.0.M1 / 2.0.0.M1 unit tests for controllers
We can now test the controller with the new test features of Grails 1.4.0.M1 / Grails 2.0.0.M1 as introduced by Peter Ledbrook on June 7th 2011 in a real Grails unit test.
so if we decide to end up with a custom marshaller like this:
public void marshalObject(Object object, XML converter) {
Foo foo = object as Foo
converter.attribute 'id', foo.id.toString()
converter.attribute 'theAge', foo.age.toString()
converter.startNode 'name'
converter.chars foo.name
converter.end()
if (foo.bars) {
converter.startNode 'customBarsElement'
converter.convertAnother foo.bars
converter.end()
}
}
Through the FooMarshallController it will output XML like this:
<foo id="1" theAge="88">
<name>nameInUnitTest</name>
<customBarsElement>
<bar id="1">
<name>firstBar</name>
</bar>
<bar id="2">
<name>secondBar</name>
</bar>
</customBarsElement>
</foo>
And we can write a Grails unit test like this
@TestFor(FooMarshallController)
@Mock([Foo, Bar])
class FooMarshallControllerTests {
void testShowContributor() {
// register the marshaller because BootStrap.groovy is not triggered in a unit test
XML.createNamedConfig('contributorApi') {
it.registerObjectMarshaller(new FooContributorMarshaller())
}
// set up some mock domain objects
Foo foo = new Foo(name: 'nameInUnitTest', age: 88)
foo.addToBars(new Bar(name: 'firstBar'))
foo.addToBars(new Bar(name: 'secondBar'))
assert foo.validate()
foo.save()
// trigger the controller
params.id = foo.id
response.format = 'xml'
controller.showContributor()
// some assertions for Foo
assert response.xml.@id == foo.id
assert response.xml.@theAge == 88.toString()
assert response.xml.name.size() == 1
assert response.xml.name[0] == 'nameInUnitTest'
// some assertions for the Bar domain relationship
assert response.xml.customBarsElement.size() == 1
assert response.xml.customBarsElement.bar.size() == 2
assert response.xml.customBarsElement.bar.find { it.name == 'firstBar'}
}
}
July 12th, 2011 at 13:52
Gr8 Post, . . . . Thanks for putting this together all in one place