Grails Goodness: Using Converter Named Configurations with Default Renderers
Sometimes we want to support in our RESTful API a different level of detail in the output for the same resource. For example a default output with the basic fields and a more detailed output with all fields for a resource. The client of our API can then choose if the default or detailed output is needed. One of the ways to implement this in Grails is using converter named configurations.
Grails converters, like JSON
and XML
, support named configurations. First we need to register a named configuration with the converter. Then we can invoke the use
method of the converter with the name of the configuration and a closure with statements to generate output. The code in the closure is executed in the context of the named configuration.
The default renderers in Grails, for example DefaultJsonRenderer
, have a property namedConfiguration
. The renderer will use the named configuration if the property is set to render the output in the context of the configured named configuration. Let’s configure the appropriate renderers and register named configurations to support the named configuration in the default renderers.
In our example we have a User
resource with some properties. We want to support short and details output where different properties are included in the resulting format.
First we have our resource:
// File: grails-app/domain/com/mrhaki/grails/User.groovy package com.mrhaki.grails import grails.rest.* // Set URI for resource to /users. // The first format in formats is the default // format. So we could use short if we wanted // the short compact format as default for this // resources. @Resource(uri = '/users', formats = ['json', 'short', 'details']) class User { String username String lastname String firstname String email static constraints = { email email: true lastname nullable: true firstname nullable: true } }
Next we register two named configurations for this resource in our Bootstrap
:
// File: grails-app/conf/Bootstrap.groovy class Bootstrap { def init = { servletContext -> ... JSON.createNamedConfig('details') { it.registerObjectMarshaller(User) { User user -> final String fullname = [user.firstname, user.lastname].join(' ') final userMap = [ id : user.id, username: user.username, email : user.email, ] if (fullname) { userMap.fullname = fullname } userMap } // Add for other resources a marshaller within // named configuration. } JSON.createNamedConfig('short') { it.registerObjectMarshaller(User) { User user -> final userMap = [ id : user.id, username: user.username ] userMap } // Add for other resources a marshaller within // named configuration. } ... } ... }
Now we must register custom renderers for the User
resource as Spring components in resources
:
// File: grails-app/conf/spring/resources.groovy import com.mrhaki.grails.User import grails.rest.render.json.JsonRenderer import org.codehaus.groovy.grails.web.mime.MimeType beans = { // Register JSON renderer for User resource with detailed output. userDetailsRenderer(JsonRenderer, User) { // Grails will compare the name of the MimeType // to determine which renderer to use. So we // use our own custom name here. // The second argument, 'details', specifies the // supported extension. We can now use // the request parameter format=details to use // this renderer for the User resource. mimeTypes = [new MimeType('application/vnd.com.mrhaki.grails.details+json', 'details')] // Here we specify the named configuration // that must be used by an instance // of this renderer. See Bootstrap.groovy // for available registered named configuration. namedConfiguration = 'details' } // Register second JSON renderer for User resource with compact output. userShortRenderer(JsonRenderer, User) { mimeTypes = [new MimeType('application/vnd.com.mrhaki.grails.short+json', 'short')] // Named configuration is different for short // renderer compared to details renderer. namedConfiguration = 'short' } // Default JSON renderer as fallback. userRenderer(JsonRenderer, User) { mimeTypes = [new MimeType('application/json', 'json')] } }
We have defined some new mime types in grails-app/conf/spring/resources.groovy
, but we must also add them to our grails-app/conf/Config.groovy
file:
// File: grails-app/conf/spring/resources.groovy ... grails.mime.types = [ ... short : ['application/vnd.com.mrhaki.grails.short+json', 'application/json'], details : ['application/vnd.com.mrhaki.grails.details+json', 'application/json'], ] ...
Our application is now ready and configured. We mostly rely on Grails content negotiation to get the correct renderer for generating our output if we request a resource. Grails content negotiation can use the value of the request parameter format
to find the correct mime type and then the correct renderer. Grails also can check the Accept
request header or the URI extension, but for our RESTful API we want to use the format
request parameter.
If we invoke our resource with different formats we see the following results:
$ curl -X GET http://localhost:8080/rest-sample/users/1 { "class": "com.mrhaki.grails.User", "id": 1, "email": "hubert@mrhaki.com", "firstname": "Hubert", "lastname": "Klein Ikkink", "username": "mrhaki" } $ curl -X GET http://localhost:8080/rest-sample/users/1?format=short { "id": 1, "username": "mrhaki" } $ curl -X GET http://localhost:8080/rest-sample/users/1?format=details { "id": 1, "username": "mrhaki", "email": "hubert@mrhaki.com", "fullname": "Hubert Klein Ikkink" }
Code written with Grails 2.4.2.
Reference: | Grails Goodness: Using Converter Named Configurations with Default Renderers from our JCG partner Hubert Ikkink at the JDriven blog. |