I had some fun creating a custom json in Grails. There is grails.web.JSONBuilder
and groovy.json.JsonBuilder
. The former seems to be deprecated (according to the grails documentation). But as it uses the same api to create json as the render
method I looked at it anyway.
Both builder do create json but they are, let’s say unintuitive, when it it comes to arrays with nested objects. If you ask google a lot of people seem to fight with this as well. Often the answers are not enlightening either.
It looks like there is no other way than to read the docs … ;-)
How hard can it be?
This blog will show a number of simple examples that will hopefully help you understand JSONBuilder
/render
and JsonBuilder
.
To concentrate on the json, the examples will be stripped of some boilerplate code and uses a few simple domain classes as test data:
JsonBuilder (groovy)
The examples are based on the following code snippets:
JsonBuilder json = new JsonBuilder ()
def map = json {
...
}
String result = json.toString ()
// json
{
....
}
JSONBuilder/render (grails)
JSONBuilder jSON = new JSONBuilder ()
JSON json = jSON.build {
....
}
String result = json.toString ()
// ... uses the same api as JSONBuilder without new,
// toString () and build
render (contentType: "text/json") {
....
}
- to reduce the noise the examples will skip the
new
and the toString ()
lines
- for easier reading the resulting json is formatted manually
Some of the examples use test data based on the following domain classes.
Example Domain Classes
class Artist {
String name
}
class Song {
String title
}
class Album {
String title
Artist artist
static hasMany = [songs:Song]
....
}
Test Data Setup
Artist prettymaids = new Artist (name: "Pretty Maids")
Album motherland = new Album (title: "Motherland", artist: prettymaids)
motherland.addToSongs (new Song (title: "Mother of all Lies"))
motherland.addToSongs (new Song (title: "To fool a Nation"))
motherland.addToSongs (new Song (title: "Confession"))
motherland.addToSongs (new Song (title: "The Iceman"))
Now let’s look at some examples..
JsonBuilder (groovy)
an empty object
def map = json {
}
// json:
{}
simple properties
def map = json {
title "Motherland"
artist "Pretty Maids"
}
// json
{
"title": "Motherland",
"artist": "Pretty Maids"
}
simple nested object
def map = json {
title motherland.title
artist {
name prettymaids.name
}
}
// json
{
"title": "Motherland",
"artist": {
"name": "Pretty Maids"
}
}
The same result is also achieved by using named arguments.
def map = json {
title motherland.title
artist (name: prettymaids.name)
}
.. more simple nesting
We can combine named arguments with the property methods.
def map = json {
title motherland.title
artist (name: prettymaids.name, country: {
name "Danmark"
})
}
// json
{
"title": "Motherland",
"artist": {
"name": "Pretty Maids",
"country": {
"name": "Danmark"
}
}
}
simple list
def map = json {
list 1,2,3,4
}
// or...
def map = json {
list ([1,2,3,4])
}
// json
{
"list": [1, 2, 3, 4]
}
So far so good.
list with objects
Now it gets a little bit strange.. A property accepts a list as value as we have seen in the previous example. If we have objects in our list we can can create an argument list for the songs
property by using the lists collect
method and converting each object to a map.
def map = json {
title motherland.title
songs motherland.songs.collect { Song s ->
[title: s.title]
}
}
// json
{
"title": "Motherland",
"songs": [{
"title": "To fool a Nation"
}, {
"title": "The Iceman"
}, {
"title": "Confession"
}, {
"title": "Mother of all Lies"
}]
}
But why a map, I want to use the builder api!
We will have to write it like this to get the same output using the builder api:
def map = json {
title motherland.title
songs motherland.songs.collect { Song s ->
json {
title s.title
}
}
}
To create json for the nested objects (the songs) using the api we call the builder (json
) again inside the collect closure. It will create the map for each song we have hand crafted in the above version.
We could also write:
def map = json {
title motherland.title
songs motherland.songs.collect { Song s ->
songs {
title s.title
}
}
}
using songs
instead of json
to call the builder. But I think that just adds to the confusion. In this case the inner songs
seems to be ignored but if we call it something else, like songs2
we will get a song
list and a simple song2
property with the last song as value.
I prefer the first version which is unintuitive enough. ;-)
As far as I understand the JsonBuilder
there is no easier way to handle nested objects. Unfortunately it is not very user friendly.
I would like to write it like this:
def map = json {
title motherland.title
songs motherland.songs, {
title s.title
}
}
The builder would loop through motherland.songs
and call the given closure to build each list items json.
JSONBuilder/render (grails)
Now a couple of similiar examples using grails.
an empty object
JSON json = jSON.build {
}
// json:
null
Using an empty json block in the render
method will fail with a NullPointerException
(in Grails).
simple properties
JSON json = jSON.build {
title = motherland.title
artist = prettymaids.name
}
// json
{
"title": "Motherland",
"artist": "Pretty Maids"
}
simple nested object
JSON json = jSON.build {
title = motherland.title
artist = {
title = prettymaids.name
}
}
// json
{
"title": "Motherland",
"artist": {
"name": "Pretty Maids"
}
}
list
JSON json = jSON.build {
title = motherland.title
songs = array {
unused {
title = "Mother of all Lies"
}
unused {
title = "To fool a Nation"
}
unused {
title = "Confession"
}
// or like this:
_ {
title = "The Iceman"
}
}
}
// json
{
"title": "Motherland",
"songs": [{
"title": "To fool a Nation"
}, {
"title": "The Iceman"
}, {
"title": "Confession"
}, {
"title": "Mother of all Lies"
}]
}
There are two things to note. First we can create lists with the explicit array
method. Second the unused
. We have to call a method on the builder to create the objects of the list, but this time the method name is not turned into a property name.
We can use anything here, it is just necessary so the closure can be called. Shortest and with fewest noise is probably to use _ {..}
to create a list object.
Of course, we can use a loop to to create the song list:
JSON json = jSON.build {
title = motherland.title
songs = array {
for (s in motherland.songs) {
_ {
title = s.title
}
}
}
}
top level list
Now as the last example there is the weird create a list as top level element construct which goes like this:
JSON json = jSON.build {
for (i in motherland.songs) {
element ([title: i.title]) // parameters must be a map
}
}
// or like this
JSON json = jSON.build {
for (i in motherland.songs) {
element {
title = i.title
}
}
}
// json
[
{"title":"The Iceman"} ,
{"title":"Mother of all Lies"},
{"title":"Confession"},
{"title":"To fool a Nation"},
]
element
is a special keyword here. Used at the top level it will create a list as the top level element.
This does not work in the render
method by the way, it will create:
{
"element": {
"title":"To fool a Nation"
}
}
That’s it.
You can get all (most) of this from the documentation. You may have to read it more than once though.. :-)
If you look after JsonBuilder
you will find the api documention and at first sight an example that leaves some open questions. But it is all there in the api docs. Make sure you look at the additional examples of each method.
JSONBuilder
and the render
method are described in the Grails documentation: creating json, render and builder api.
JSONBuilder
/render
s explicit array method is a lot easier to understand than the collect
expression we had to use with JsonBuilder
, but the unused property on the list object in both builders is confusing from a user perspective.
Personally I prefer JsonBuilder
s notation. I only dislike the way we have to handle arrays. It would be a lot easier to understand if we could just write:
def map = json {
songs motherland.songs, {
title s.title
}
}
Running git clone git@github.com:hauner/groovy-core.git groovy-core.git
now …. :-)
Update: May 2014
My patch for the improved array handling was merged and is part of groovy 2.3.0.