Monday, December 5, 2011

Groovy safe navigation gotcha

I just came across this Groovy gotcha today while accessing a list within a chain of attributes. I had a statement like this in a gsp:
${invoice?.invoiceItems[0]?.name}

This is the (erroneous) recommended style when data binding many ended associations in Grails, but it could easily arise in other scenarios. If I try this "safe" statement where invoiceItems is null, I'll see a null pointer exception. In fact I'll even see it when invoice is null. The reason for the exception is a combination of how lists and the safe navigation work in Groovy.

Lists are much more elegant in Groovy than Java, as they share the square brackets that primitive arrays use. Creating a list is as easy as writing:
def x = [1,2,3]

Just like an array you can access the first element of this list like so:
println x[0]

However, unlike a primitive array the [] operator on a list is mapped (by some Groovy magic) behind the scenes to the getAt method of the particular List object. We can expand our example as follows:
${invoice?.invoiceItems.getAt(0).name}


Safe navigation is a beautiful, and elegant means of protecting your code for scenarios where a variable could be null. It doesn't work as you might expect though. In my example above if invoice is null you might expect that after discovering invoice to be null execution would move to the next statement. Instead what happens is that each part of the chain is executed in turn, and the ? is equivalent to a try/catch block, where if a null pointer exception is caught, the exception will be swallowed, and null returned.

The end result of these two Groovy behaviors is that in my example (and indeed the examples in the grails documentation) we will see the attempted execution of
null.getAt(0)
The solution is to avoid using the [] operator when accessing lists and to place a ? before every full stop in your statement chain.
${invoice?.invoiceItems?.getAt(0)?.name}


Credits


http://grails.org/doc/latest/guide/6.%20The%20Web%20Layer.html#6.1.6 Data Binding

No comments:

Post a Comment