Groovy: isn't there a stringToMap out of the box?
Asked Answered
S

5

31

as a tcl developer starting with groovy, I am a little bit surprised about the list and map support in groovy. Maybe I am missing something here.

I am used to convert between strings, lists and arrays/maps in tcl on the fly. In tcl, something like

"['a':2,'b':4]".each {key, value -> println key + " " + value}

would be possible, where as in groovy, the each command steps through each character of the string.

This would be much of a problem is I could easily use something like the split or tokenize command, but because a serialized list or map isn't just "a:2,b:4", it is a little bit harder to parse.

It seems that griffon developers use a stringToMap library (http://code.google.com/p/stringtomap/) but the example can't cope with the serialized maps either.

So my question is now: what's the best way to parse a map or a list in groovy?

Cheers, Ralf

PS: it's a groovy question, but I've tagged it with grails, because I need this functionality for grails where I would like to pass maps through the URL

Update: This is still an open question for me... so here are some updates for those who have the same problem:

  • when you turn a Map into a String, a .toString() will result in something which can't be turned back into a map in all cases, but an .inspect() will give you a String which can be evaluated back to a map!
  • in Grails, there is a .encodeAsJSON() and JSON.parse(String) - both work great, but I haven't checked out yet what the parser will do with JSON functions (possible security problem)
Sharpsighted answered 6/2, 2010 at 9:3 Comment(4)
If you're using grails and want a map, I'd look at POSTing a JSON message. Probably easier to generate on the client side and there are things built into grails to evaluate JSON.Stannum
thanx. json could indeed be a very good alternative!Sharpsighted
Passing maps through the URL in Grails sounds like a job for URL mappings. Check out the Embedded Variables section of the grails user guide (section 6.4.2). You can define a custom URL structure to pass whatever map you'd like, ie myapp.com/controller/action/key1/value1/key2/value2 It won't work too well for multi-dimensional maps, or huge data structures, but I would contend those shouldn't be passed around via URLs anyway.Bean
good idea - when you have small maps. In my case, I would like to submit the data to be plotted on a chart through the URL. Something like the google chart API. So my maps can be quite big...Sharpsighted
U
21

Not exactly native groovy, but useful for serializing to JSON:

import groovy.json.JsonBuilder
import groovy.json.JsonSlurper

def map = ['a':2,'b':4 ]
def s = new JsonBuilder(map).toString()
println s

assert map == new JsonSlurper().parseText(s)

with meta-programming:

import groovy.json.JsonBuilder
import groovy.json.JsonSlurper

Map.metaClass.toJson   = { new JsonBuilder(delegate).toString() }
String.metaClass.toMap = { new JsonSlurper().parseText(delegate) }

def map = ['a':2,'b':4 ]
assert map.toJson() == '{"a":2,"b":4}'
assert map.toJson().toMap() == map

unfortunately, it's not possible to override the toString() method...

Uphroe answered 8/7, 2014 at 15:26 Comment(1)
I guess this is a clean solution... with a little bit of meta programming, this functionality can be easily added to the String class...Sharpsighted
P
33

You might want to try a few of your scenarios using evaluate, it might do what you are looking for.

def stringMap = "['a':2,'b':4]"
def map = evaluate(stringMap)

assert map.a == 2
assert map.b == 4

def stringMapNested = "['foo':'bar', baz:['alpha':'beta']]"
def map2 = evaluate(stringMapNested)

assert map2.foo == "bar"
assert map2.baz.alpha == "beta"
Participle answered 6/2, 2010 at 17:14 Comment(6)
thanx. I guess this is close to what I was looking for!Sharpsighted
wow. just noticed that the solution is quite a big security risk! If the data is passed through the URL and we just do an evaluate() on it, everything can happen! So I guess the JSON solution will be the better way...Sharpsighted
You can use Eval class that is a simple helper on top of GroovyShell: def map = Eval.me("['a':2,'b':4]")Miksen
I wanted to parse this myself, this is awesomeSubcortex
Great comment @ArturoHerrero! ThanksEureetloir
This didn't work in my Groovy/Spring project but Eval.me(stringMap) did work.Peroxidase
U
21

Not exactly native groovy, but useful for serializing to JSON:

import groovy.json.JsonBuilder
import groovy.json.JsonSlurper

def map = ['a':2,'b':4 ]
def s = new JsonBuilder(map).toString()
println s

assert map == new JsonSlurper().parseText(s)

with meta-programming:

import groovy.json.JsonBuilder
import groovy.json.JsonSlurper

Map.metaClass.toJson   = { new JsonBuilder(delegate).toString() }
String.metaClass.toMap = { new JsonSlurper().parseText(delegate) }

def map = ['a':2,'b':4 ]
assert map.toJson() == '{"a":2,"b":4}'
assert map.toJson().toMap() == map

unfortunately, it's not possible to override the toString() method...

Uphroe answered 8/7, 2014 at 15:26 Comment(1)
I guess this is a clean solution... with a little bit of meta programming, this functionality can be easily added to the String class...Sharpsighted
P
4

I think you are looking for a combination of ConfigObject and ConfigSlurper. Something like this would do the trick.

def foo = new ConfigObject()
foo.bar = [ 'a' : 2, 'b' : 4 ]

// we need to serialize it
new File( 'serialized.groovy' ).withWriter{ writer ->
  foo.writeTo( writer )
}

def config = new ConfigSlurper().parse(new File('serialized.groovy').toURL())    

// highest level structure is a map ["bar":...], that's why we need one loop more
config.each { _,v ->
    v.each {key, value -> println key + " " + value}
}
Prunelle answered 6/2, 2010 at 9:53 Comment(0)
T
2

I hope this help:

    foo= "['a':2,'b':4]"
    Map mapResult=[:]
    mapResult += foo.replaceAll('\\[|\\]', '').split(',').collectEntries { entry ->
        def pair = entry.split(':')
        [(pair.first().trim()): pair.last().trim()]
       }
Trusteeship answered 8/10, 2019 at 19:17 Comment(0)
D
1

If you don't want to use evaluate(), do instead:

def stringMap = "['a':2,'b':4]"
stringMap = stringMap.replaceAll('\\[|\\]','')
def newMap = [:]
stringMap.tokenize(',').each {
kvTuple = it.tokenize(':')
newMap[kvTuple[0]] = kvTuple[1]
}
println newMap
Dregs answered 6/2, 2010 at 9:3 Comment(1)
That is a pretty limited as it will only work with Strings. For example, in your example the values that are put in newMap will be the Strings "2" and "4", not the numbers 2 and 4.Warram

© 2022 - 2024 — McMap. All rights reserved.