Friday, September 12, 2008

Rendering complex collections as JSON in Grails

So I have a set of somewhat complex objects I need to render as JSON in Grails for some AJAX calls. Let's say I have an array of "Foo" objects, each of which contains a name and an array of "Bar" objects, each of which have a name and a location.

I want the JSON to look something like this (granted, there are a number of ways I could probably use simpler data structures with more frequent AJAX calls, but I'm trying to make as few trips to the server as possible) :

{"foos":
[
{
"fooName":"fooOne",
"bars":[
{"barName":"barOne","location":"here"},
{"barName":"barTwo","location":"there"},
{"barName":"barThree","location":"everywhere"}
]
},
{
"fooName":"fooTwo",
"bars":[
{"barName":"barFour","location":"Istanbul"},
{"barName":"barFive","location":"Constantiople"},
{"barName":"barSix","location":"Nobody's Business"}
]
}
]
}

I tried for a while to get this to render using Grails JSONBuilder, but to no avail: the secondary arrays kept being built outside the Foo objects. Eventually, I came across this really helpful post: Mini-Guide to rendering JSON with Grails that pretty much described the things I had tried and the problems I encountered. The post suggested trying the JSON converter that's bundled with Grails and gave a fairly simple example of how to use it.

The JSON I wanted to build was a bit more complex than the example, though, so I went searching for more examples of how to use the JSON converter. In the examples I found, most of the structures didn't go as deeply as the one I wanted; most of them just included a list at the top level of the structure, using the Grails "list" method (e.g. Books.list(params), which would get you the listing of books with the param IDs). I needed to go one step deeper, so I thought I'd see if Grails would automatically recurse the Lists in my objects. No such luck: when I tried rendering like this "render foos as JSON", the first-level items rendered correctly, but the array of Bars was empty.

Finally, I just decided to try constructing the data structures myself since maybe the JSON converter had trouble navigating object graphs but not basic collections. Since Javascript doesn't really differentiate between Objects and Maps (which is basically how JSON works), I created a Map for each Bar, put them into an array which then went into a Foo Map containing the attributes for the Foo. Finally, I created an array of the Foo Maps, which I then rendered using the JSON converter.

That actually worked! The downside, though, is that this is pretty fragile: if the data I want to return from the Foo or Bar objects changes, I have to change how the containers are rendered.

I don't know how much of what I encountered in JSONBuilder or the JSON converter is by design (maybe trying to force less complex data structures), but it certainly would be helpful to be able to create these kinds of arbitrarily complex structures based on the objects themselves rather than having to create our own containers. I'll have to see if anyone is already working on that.

Here's the final code I ended up with (it's probably not idiomatic Groovy, but I'm more familiar with Java, so it was quick):

import grails.converters.JSON
. . .
def getJson = {
def fooList = new ArrayList()
data.foos.each { foo ->

def fooMap = new HashMap()
fooMap.put("fooName", foo.fooName)

// see what bars this foo has
def bars = new ArrayList()
foo.bars.each { bar ->
def barMap = new HashMap()
barMap.put("barName", bar.barName)
barMap.put("location", bar.location)
bars.add(barMap)
}
fooMap.put("bars", bars)

fooList.add(fooMap)
}

def output = [
foos: fooList
]

render output as JSON
}

1 comment:

VocĂȘ said...

I've been struggling to find a good way to build complex JSON objects using the converters. Your solution worked like a charm. Thank you very much!