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,40&cht=p3&chl=Hello|World

There is a base URL ( 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.

def base = ''

def params = [chs:'250x100', chd:'t:60,40', cht:'p3', chl:'Hello|World']

def url = base + params.collect { k,v -> "$k=$v" }.join('&')

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.

[x:1, y:2, z:3].each { k,v -> println "$k=$v" }
[x:1, y:2, z:3].each { println it }

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

def url = base + params.collect { k,v -> "$k=$v" }.join('&')

all I really need to write is

def url = base + params.collect { it }.join('&')

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 (new Testing chapter added this week).

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

About Ken Kousen
I am a Java Champion and the author of the books "Modern Java Recipes" (O'Reilly Media), "Gradle Recipes for Android" (O'Reilly Media), and "Making Java Groovy" (Manning), as well as over a dozen video courses at Safari Books Online. I'm a regular member of the No Fluff, Just Stuff conference tour and have given talks all over the world. Through my company, Kousen IT, Inc, I've taught training courses to and worked with thousands of developers.

7 Responses to Converting Groovy maps to query strings

  1. Paul King says:

    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. Pingback: Blog IT – Mateusz Mrozewski » Daily Dose of Reading #2

  3. Brian Doyle says:

    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×100&chd=t:60,40&cht=p3&chl=Hello|World&

  5. Ken Kousen says:

    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. brianpdoyle says:

    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. Pingback: GroovyMag - the magazine for Groovy and Grails developers about Groovy programming and Grails development

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: