Mini guide to rendering JSON with Grails
Grails has built-in support for a JSON building DSL, which, together with the render controller dynamic method, makes rendering JSON responses an enjoyable job. But there seems to be a lack of consolidated information in the official documentation to clarify all the bits about getting anything you want in JSON. So here’s my little mini guide to share with you.
Ground Rules
As the JSON spec reads,
JSON is built on two structures:
- A collection of name/value pairs. In various languages, this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
- An ordered list of values. In most languages, this is realized as an array, vector, list, or sequence.
So there are only two rules for rendering JSON with Grails.
1. To render a collection of name/value pairs (an object), use the method call syntax.
e.g.
render(contentType:'text/json'){
pair(name:'value')
}
Will be rendered as
{"pair":{"name":"value"}}
2. To render an array of values, use a closure
e.g.
render(contentType:'text/json'){
collection{
pair(name:'value')
pair(name:'value1')
}
}
Will be rendered as
{collection:[{"name":"value"},{"name":"value1"}]}
Anything inside the collection closure becomes the values of the array. And because of this, the method name “pair” has no place to appear in the rendered array.
Putting It to Work
Here’s an example putting the two rules together.
render(contentType:'text/json'){
studio(name:'Pixar',website:'pixar.com')
films{
film(title:'Toy Story',year:'1995')
film(title:'Monsters, Inc.',year:'2001')
film(title:'Finding Nemo',year:'2003')
}
}
The result JSON will be
{"studio":{"name":"Pixar","website":"pixar.com"},
"films":[
{"title":"Toy Story","year":"1995"},
{"title":"Monsters, Inc.","year":"2001"},
{"title":"Finding Nemo","year":"2003"}
]}
When DSL is Not Enough
But there’s a gotcha about the current JSON Builder DSL to keep in mind. As of 1.0 RC3, You can have objects inside an array, but not an array inside an object (except the JSON object itself).
For example, I want a JSON structure like this.
{"object":{"collection":[{"name":"value1"},{"name":"value2"}]}}
I may write something like this,
render(contentType:'text/json'){
object(collection{
item(name:'value1')
item(name:'value2')
})
}
Although it’s syntactically correct, you will get a runtime exception of
java.lang.IllegalArgumentException: JSON Builder: not implemented
at grails.util.JSonBuilder.createNode(JSonBuilder.java:121)
...
Oh, maybe I made a mistake, I think. The closure is not a key/value pair there. And I change the code like this
render(contentType:'text/json'){
object(collection:{
item(name:'value1')
item(name:'value2')
})
}
This looks better. Does it work? Nope. It’s not what you’d expect.
{"object":{"collection":"DummyController$_closure15_closure27_closure28@4af40c"}}
This can’t be right. Then how about
render(contentType:'text/json'){
object(collection:collection{
item(name:'value1')
item(name:'value2')
})
}
Oddly, this will be rendered as
{"collection":[{"name":"value1"},{"name":"value2"}],"object":{"collection":1}}
The Almighty JSON converter
So is there any workaround, when JSON Builder DSL fails you? Yes! You can always turn to the JSON converter. Construct a temporary Map to contain the data, and use the converter to render the map as a whole.
To correctly render the JSON data in the previous section, you can use the JSON converter this way. In your controller, use the following code.
import grails.converters.*
...
def jsonMethod = {
...
def json = [object:[collection:[[name:'value1'],[name:'value2']]]]
render json as JSON
}
The result JSON will be exactly what I want
{"object":{"collection":[{"name":"value1"},{"name":"value2"}]}}
You can render any object using the JSON converter. And the results are pretty predictable.
Performance-wise, I’m not sure if there’s any difference or anyone has done any benchmark yet. So I’d say that it’s really your call which way to make your JSON. The converter doesn’t look as sexy as the DSL, but it sure works great. I may just choose either one depending on my mood:)
Tags: dsl, Grails, javascript, json