Accessing map inside map with closure,
I have a map object the values is another map object
e.g:-
`
to access the data like this I can issue
def map = [name:"Gromit", likes:"cheese", id:1234]
def map2 =[map1:map]
map2.each{entry ->
println entry.key
entry.value.each {entry1 -> println entry1.key
println entry1.value
}
}
to access a single map i can issue
map.each{entry ->
println entry.key
println entry.value
}
'
How can I write a DSL for the above map example in simple any hint?
Here is an illustration of printing the keys and values of the inner map. Try this:
map1=new HashMap()
map2=new HashMap()
map2.put("1","one")
map1.put("map2",map2)
map1.each{ entry1 ->
def innerMap = entry1.value
innerMap.each { entry2 ->
println "key is ${entry2.key}"
println "value is ${entry2.value}"
}
}
anish, I assume you look for a shorter way to access the map, this would be map2.map1. You can then use map2.map1.name to get "Gromit". If a shorter way to get the map was not your question, then please specify more.
Related
I have two map in the following way
def map1 = ['a':1,'b':2]
def map2 = ['a':345,'c':10,'b':1]
I would like to create a result map which would look basically look to match the keys of the two maps and would make the value of map1 as the key and value of map2 as the value itself. The output would look like this:
map3=[1:345,2:1]
You can do this easily with a simple loop:
map3 = map1.collectEntries { key, val -> [(val): map2[key]] }
I'm trying to use the Groovy way of creating a TreeMap<String, List<Data>> with default values so I easily add data to a new list if the key isn't already present.
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>) [:].withDefault { [] }
As you can see, I have the requirement to use a TreeMap and withDefault only returns a Map instance, so I need to cast.
When I attempt to add a new list to the map,
myData[newKey].add(newData)
myData[newKey] is null. However, if I change my Map initilization to remove the TreeMap cast (and change the type to just Map instead of TreeMap), myData[newKey].add(newData) works as expected.
What's the reasoning for this? Can I not use withDefault if I cast the map?
The problem isn't just about the cast. It also has to do with the declared type. The problem can be simplified to something like this:
def map1 = [:].withDefault { 0 }
TreeMap map2 = map1
When that is executed map1 is an instance of groovy.lang.MapWithDefault and map2 is an instance of java.util.TreeMap. They are 2 separate objects on the heap, not just 2 references pointing to the same object. map2 will not have any default behavior associated with it. It is as if you had done this:
def map1 = [:].withDefault { 0 }
TreeMap map2 = new TreeMap(map1)
That is what is happening with your code. The cast and the generics just makes it less clear with your code.
This:
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>) [:].withDefault { [] }
Can be broken down to this:
def tmpMap = [:].withDefault { [] }
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>)tmpMap
I hope that helps.
EDIT:
Another way to see the same thing happening is to do something like this:
Set names = new HashSet()
ArrayList namesList = names
When the second line executes a new ArrayList is created as if you had done ArrayList namesList = new ArrayList(names). That looks different than what you have in your code, but the same sort of thing is happening. You have a reference with a static type associated with it and are pointing that reference at an object of a different type and Groovy is creating an instance of your declared type. In this simple example above, that declared type is ArrayList. In your example that declared type is TreeMap<String, List<Data>>.
When using a map of closures to implement an interface in Groovy (as in http://groovy.codehaus.org/Groovy+way+to+implement+interfaces) is there any way to convert the object back to a map after using the as keyword or the asType method to implement the interface?
Based on your use case it would seem that you could just keep a reference to the original Map before converting it into the needed interface.
However, looking at the source code that converts the Map object into the interface (using a Proxy), it looks like you can just re-retrieve the original map by getting the InvocationHandler's delegate.
def i = 1
def m = [ hasNext:{ true }, next:{ i++ } ]
Iterator iter = m as Iterator
def d = java.lang.reflect.Proxy.getInvocationHandler(iter).delegate
assert d.is(m)
Note: This depends on the internals of the Groovy code so use at your own risk:
Interesting question... Short answer, no. Long answer, maybe... Assuming you have something like this:
def i = 1
Iterator iter = [ hasNext:{ true }, next:{ i++ } ] as Iterator
then calling
println iter.take( 3 ).collect()
prints [1,2,3]
Now, you can declare a method to do this:
def mapFromInterface( Object o, Class... clz ) {
// Get a Set of all methods across the array of classes clz
Set methods = clz*.methods.flatten()
// Then, for each of these
methods.collectEntries {
// create a map entry with the name of the method as the key
// and a closure which invokes the method as a value
[ (it.name): { Object... args ->
o.metaClass.pickMethod( it.name, it.parameterTypes ).invoke( o, args )
} ]
}
}
This then allows you to do:
def map = mapFromInterface( iter, Iterator )
And calling:
println map.next()
println map.next()
Will print 4 followed by 5
printing the map with println map gives:
[ remove:ConsoleScript43$_mapFromInterface_closure3_closure4#57752bea,
hasNext:ConsoleScript43$_mapFromInterface_closure3_closure4#4d963c81,
next:ConsoleScript43$_mapFromInterface_closure3_closure4#425e60f2 ]
However, as this is a map, any class which contains multiple methods with the same name and different arguments will fail. I am also not sure how wise it is to do this in the first case...
What is your use-case out of interest?
This is OK
def variables=[
['var1':'test1'],
['var2':'test2'],
['var3':'test3']
]
println "${variables.size()}"
variables.each{entry ->
println "${entry} "
}
I got:
3
[var1:test1]
[var2:test2]
[var3:test3]
but this caused problems
def variables=[
['var1':'test1'],
['var2':'test2'],
['var3':'test3']
]
println "${variables.size()}"
variables.each{entry ->
println "${entry.key} "
}
since I got:
3
null
null
null
I'm expecting:
3
var1
var2
var3
what's wrong with my code?
thank you!!!
You want:
def variables=[
'var1':'test1',
'var2':'test2',
'var3':'test3'
]
println variables.size()
variables.each{entry ->
println entry.key
}
Before you had an ArrayList containing three LinkedHashMap objects. The above code is a single LinkedHashMap with three entries. You also don't need string interpolation, so I removed it.
Matthew's solution works great, and it's probably what you wanted (a simpler data structure to begin with).
However, in case you really wanted variables to be a list of three maps (as per your question), then this how you could get your desired output:
def variables=[
['var1':'test1'],
['var2':'test2'],
['var3':'test3']
]
println "${variables.size()}"
variables.each{ entry->
entry.each {
println it.key
}
}
In the outer closure, every entry is a map. So we iterate through each of those maps using the inner closure. In this inner closure, every it closure-param is a key:value pair, so we just print its key using it.key.
Like Matthew, I've also removed the string interpolation since you don't need it.
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)
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"
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...
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}
}
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
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()]
}