Converting Groovy maps to query strings

Yesterday I was teaching a class on Groovy when I suddenly realized there was a simpler way to do something I’d been doing for years.

I like showing developers how easy it is to access RESTful web services with just a couple of lines of Groovy. The process is particularly simple if all you need is a GET request, since you can just convert a String to a URL and use the getText method to retrieve the response.

For example, consider the Google Chart Tools service. The idea there is that you can send your data in a URL to Google and the service will return a graphical image of the data. The “Hello, World!” example from the documentation looks like

https://chart.googleapis.com/chart?chs=250x100&chd=t:60,40&cht=p3&chl=Hello|World

There is a base URL (https://chart.googleapis.com/chart) followed by a query string with lots of parameters. In this particular case, the parameters are chs (the chart size, 250×100 pixels), chd (chart data, here 60 and 40), cht (the chart type, here a 3D pie chart), and chl (the chart labels, “Hello” and “World” separated by vertical bars). If you transmit this URL to Google, it will respond with a nice image.

It’s easiest to see that by simply embedding the URL as the href attribute of an <img> tag.

From a Groovy point of view, the URL looks like a base string plus a map that has been converted into a query string. I would write a script like the following to assemble it.

[sourcecode language=”groovy”]
def base = ‘https://chart.googleapis.com/chart?&#8217;

def params = [chs:’250×100′, chd:’t:60,40′, cht:’p3′, chl:’Hello|World’]

def url = base + params.collect { k,v -> "$k=$v" }.join(‘&’)
[/sourcecode]
Nice and simple. The collect method applies the closure to the params map, transforming each entry from a key, value pair into the string “key=value” and returns the resulting list. Then the join method produces a string by connecting each of the list elements with the ampersand delimiter. I’ve been telling people that this particular combination of collect method, closure, and join is a very useful way to assemble a query string from a map of parameters.

Yesterday, however, I was showing the students that according to the Groovy JDK there are two versions of the each iterator on java.util.Map. One version takes a two argument closure that automatically separates the keys from the values, just as I did above. The other uses a single argument closure, which treats each element as an instance of Map.Entry, so that you have to call it.key or it.value (i.e., it.getKey() and it.getValue()) to get the keys and values individually.

To illustrate the point, I took a trivial map and printed out the keys and values, and then I printed out the map entries, at which point I got a surprise.
[sourcecode language=”groovy”]
[x:1, y:2, z:3].each { k,v -> println "$k=$v" }
[x:1, y:2, z:3].each { println it }
[/sourcecode]
Lo and behold, the map entries printed out in the form “key=value”. In other words, the toString method on Map.Entry prints exactly what I needed for the query string.

It then occurred to me to look and see if all the other methods on Map (like find, findAll, count, sort, and especially collect) also had both one- and two-argument closure versions, just like each. And, sure enough, they all did.

In other words, instead of
[sourcecode language=”groovy”]
def url = base + params.collect { k,v -> "$k=$v" }.join(‘&’)
[/sourcecode]
all I really need to write is
[sourcecode language=”groovy”]
def url = base + params.collect { it }.join(‘&’)
[/sourcecode]
and I get the same result. The collect method transforms the map into a list whose elements are the map entries, and then the join method invokes toString on each and assembles the query string out of the result.

Wow. As simple as my original code was, I’ve been doing it the hard way all along.

Of course the down side is that now I need to go back and revise all my similar examples in my book, Making Java Groovy, available in Early Access Form from Manning at http://manning.com/kousen (new Testing chapter added this week).

That’s a small price to pay for discovering that Groovy is even simpler than I realized. 🙂

7 responses to “Converting Groovy maps to query strings”

  1. Some other options (grep only being available in recent versions of Groovy):

    [x:1, y:2, z:3].grep().join(‘&’),
    [x:1, y:2, z:3].iterator().join(‘&’),
    [x:1, y:2, z:3].entrySet().join(‘&’)

    Cheers, Paul.

  2. […] Converting Groovy maps to query strings […]

  3. I think you’ll need to URL encode the values so it would be something like:

    params.collect { k,v -> “$URLEncoder.encode(k)=$URLEncoder.encode(v)” }.join(‘&’)

    or if you’re using Grails it would be:

    params.collect { k,v -> “k.encodeAsURL()=v.encodeAsURL()” }.join(‘&’)

  4. I’ve just recently found out as of 1.8.1 inject() method is available on Maps. With that you can have the following solution:

    def url = params.inject(base){b,e -> b += “$e&”}

    although you end up with an extra
    https://chart.googleapis.com/chart?chs=250×100&chd=t:60,40&cht=p3&chl=Hello|World&

  5. Thanks for the feedback. As an aside, I normally URL encode the data before I assemble the parameters map, which I why I didn’t show it in my example. I should mention, though, that the one-argument URLEncoder.encode method is deprecated. You’re supposed to use URLEncoder.encode(it,’UTF-8′) instead.

  6. Ken, sounds good about the encoding of the params and thanks for the tip on the deprecated encode method. I kinda wish they would just make UTF-8 as the default.

  7. […] Kousen has put up another classic ( ) “Stuff I’ve learned recently…” post (“Yesterday I was teaching a class on Groovy when I suddenly realized there was a simpler way to […]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Stuff I've learned recently...

Subscribe now to keep reading and get access to the full archive.

Continue reading