9.1.6 Customizing Response Rendering - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith, Lari Hotari
Version: 3.0.11
Table of Contents
9.1.6 Customizing Response Rendering
There are several ways to customize response rendering in Grails.9.1.6.1 Customizing the Default Renderers
The default renderers for XML and JSON can be found in thegrails.rest.render.xml
and grails.rest.render.json
packages respectively. These use the Grails converters (grails.converters.XML
and grails.converters.JSON
) by default for response rendering.You can easily customize response rendering using these default renderers. A common change you may want to make is to include or exclude certain properties from rendering.Including or Excluding Properties from Rendering
As mentioned previously, Grails maintains a registry ofgrails.rest.render.Renderer
instances. There are some default configured renderers and the ability to register or override renderers for a given domain class or even for a collection of domain classes. To include a particular property from rendering you need to register a custom renderer by defining a bean in grails-app/conf/spring/resources.groovy
:import grails.rest.render.xml.*beans = { bookRenderer(XmlRenderer, Book) { includes = ['title'] } }
The bean name is not important (Grails will scan the application context for all registered renderer beans), but for organizational and readability purposes it is recommended you name it something meaningful.To exclude a property, the
excludes
property of the XmlRenderer
class can be used:import grails.rest.render.xml.*beans = { bookRenderer(XmlRenderer, Book) { excludes = ['isbn'] } }
Customizing the Converters
As mentioned previously, the default renders use thegrails.converters
package under the covers. In other words, under the covers they essentially do the following:import grails.converters.*…
render book as XML// or render book as JSON
9.1.6.2 Registering Custom Objects Marshallers
Grails' Converters feature the notion of an ObjectMarshaller and each type can have a registeredObjectMarshaller
. You can register custom ObjectMarshaller
instances to completely customize response rendering. For example, you can define the following in BootStrap.init
:XML.registerObjectMarshaller Book, { Book book, XML xml -> xml.attribute 'id', book.id xml.build { title(book.title) } }
JSON.registerObjectMarshaller(DateTime) { return it?.toString("yyyy-MM-dd'T'HH:mm:ss'Z'") }
JSON.registerObjectMarshaller(Book) {
def map= [:]
map['titl'] = it.title
map['auth'] = it.author
return map
}
Registering Custom Marshallers via Spring
Note that if you have many custom marshallers it is recommended you split the registration of these into a separate class:class CustomMarshallerRegistrar { @javax.annotation.PostConstruct void registerMarshallers() { JSON.registerObjectMarshaller(DateTime) { return it?.toString("yyyy-MM-dd'T'HH:mm:ss'Z'") } } }
grails-app/conf/spring/resources.groovy
:beans = { myCustomMarshallerRegistrar(CustomMarshallerRegistrar) }
PostConstruct
annotation will get triggered on startup of your application.
9.1.6.3 Using Named Configurations for Object Marshallers
It is also possible to register named configurations. For example:XML.createNamedConfig('publicApi') { it.registerObjectMarshaller(Book) { Book book, XML xml -> // do public API } } XML.createNamedConfig('adminApi') { it.registerObjectMarshaller(Book) { Book book, XML xml -> // do admin API } }
render
or respond
methods you can wrap the call in a named configuration if necessary to customize rendering per request:XML.use( isAdmin ? 'adminApi' : 'publicApi') { render book as XML }
XML.use( isAdmin ? 'adminApi' : 'publicApi') { respond book }
9.1.6.4 Implementing the ObjectMarshaller Interface
For more complex marshallers it is recommended you implement the ObjectMarshaller interface. For example given a domain class:class Book {
String title
}
render book as XML
<book id="1"> <title>The Stand</title> </book>
class BookMarshaller implements ObjectMarshaller<XML> { public boolean supports(Object object) { return object instanceof Book } public void marshalObject(Object object, XML converter) { Book book = (Book)object converter.chars book.title } }
XML.registerObjectMarshaller(new BookMarshaller())
ObjectMarshaller
in place, the output is now:<book>The Stand</book>
Customizing the Name of the Root Element
If you wish the customize the name of the surrounding element, you can implement NameAwareMarshaller:class BookMarshaller implements ObjectMarshaller<XML>,NameAwareMarshaller { ... String getElementName(Object o) { return 'custom-book' }}
<custom-book>The Stand</custom-book>
Outputting Markup Using the Converters API or Builder
With the passed Converter object you can explicitly code to the Converters API to stream markup to the response:public void marshalObject(Object object, XML converter) { Book book = (Book)object converter.attribute 'id', book.id.toString() converter.attribute 'date-released', book.dateReleased.toString() converter.startNode 'title' converter.chars book.title converter.end()}
<book id="1" date-released="..."> <title>The Stand</title> </book>
CompileStatic
):public void marshalObject(Object object, XML converter) { Book b = (Book)object converter.build { book(id: b.id) { title b.title } } }
Using the convertAnother Method to Recursively Convert Objects
To create more complex responses you can use theconvertAnother
method to convert associations and other objects:public void marshalObject(Object object, XML converter) { Book book = (Book)object converter.startNode 'title' converter.chars book.title converter.end() if (book.authors) { converter.startNode 'authors' for(author in book.authors) { converter.convertAnother author } converter.end() } }
9.1.6.5 Implementing a Custom Renderer
If you want even more control of the rendering or prefer to use your own marshalling techniques then you can implement your ownRenderer
instance. For example below is a simple implementation that customizes the rendering of the Book
class:package myapp import grails.rest.render.* import grails.web.mime.MimeTypeclass BookXmlRenderer extends AbstractRenderer<Book> { BookXmlRenderer() { super(Book, [MimeType.XML,MimeType.TEXT_XML] as MimeType[]) } void render(Book object, RenderContext context) { context.contentType = MimeType.XML.name def xml = new groovy.xml.MarkupBuilder(context.writer) xml.book(id: object.id, title:object.title) } }
AbstractRenderer
super class has a constructor that takes the class that it renders and the MimeType
(s) that are accepted (via the ACCEPT header or file extension) for the renderer.To configure this renderer, simply add it is a bean to grails-app/conf/spring/resources.groovy
:beans = { bookRenderer(myapp.BookXmlRenderer) }
Book
instances will be rendered in the following format:<book id="1" title="The Stand"/>
Note that if you change the rendering to a completely different format like the above, then you also need to change the binding if you plan to support POST and PUT requests. Grails will not automatically know how to bind data from a custom XML format to a domain class otherwise. See the section on "Customizing Binding of Resources" for further information.
Container Renderers
Agrails.rest.render.ContainerRenderer
is a renderer that renders responses for containers of objects (lists, maps, collections etc.). The interface is largely the same as the Renderer
interface except for the addition of the getComponentType()
method, which should return the "contained" type. For example:class BookListRenderer implements ContainerRenderer<List, Book> { Class<List> getTargetType() { List } Class<Book> getComponentType() { Book } MimeType[] getMimeTypes() { [ MimeType.XML] as MimeType[] } void render(List object, RenderContext context) { .... } }
9.1.6.6 Using GSP to Customize Rendering
You can also customize rendering on a per action basis using Groovy Server Pages (GSP). For example given theshow
action mentioned previously:def show(Book book) { respond book }
show.xml.gsp
file to customize the rendering of the XML:<%@page contentType="application/xml"%> <book id="${book.id}" title="${book.title}"/>