groovy: safely find a key in a map and return its value

131,701

Solution 1

def mymap = [name:"Gromit", id:1234]
def x = mymap.find{ it.key == "likes" }?.value
if(x)
    println "x value: ${x}"

println x.getClass().name

?. checks for null and does not create an exception in Groovy. If the key does not exist, the result will be a org.codehaus.groovy.runtime.NullObject.

Solution 2

The whole point of using Maps is direct access. If you know for sure that the value in a map will never be Groovy-false, then you can do this:

def mymap = [name:"Gromit", likes:"cheese", id:1234]
def key = "likes"

if(mymap[key]) {
    println mymap[key]
}

However, if the value could potentially be Groovy-false, you should use:

if(mymap.containsKey(key)) {
    println mymap[key]
}

The easiest solution, though, if you know the value isn't going to be Groovy-false (or you can ignore that), and want a default value, is like this:

def value = mymap[key] ?: "default"

All three of these solutions are significantly faster than your examples, because they don't scan the entire map for keys. They take advantage of the HashMap (or LinkedHashMap) design that makes direct key access nearly instantaneous.

Solution 3

In general, this depends what your map contains. If it has null values, things can get tricky and containsKey(key) or get(key, default) should be used to detect of the element really exists. In many cases the code can become simpler you can define a default value:

def mymap = [name:"Gromit", likes:"cheese", id:1234]
def x1 = mymap.get('likes', '[nothing specified]')
println "x value: ${x}" }

Note also that containsKey() or get() are much faster than setting up a closure to check the element mymap.find{ it.key == "likes" }. Using closure only makes sense if you really do something more complex in there. You could e.g. do this:

mymap.find{ // "it" is the default parameter
  if (it.key != "likes") return false
  println "x value: ${it.value}" 
  return true // stop searching
}

Or with explicit parameters:

mymap.find{ key,value ->
  (key != "likes")  return false
  println "x value: ${value}" 
  return true // stop searching
}

Solution 4

You forgot to mention what happens when the key doesn't exist. I'm going to assume null is Ok.


Groovy maps can be queried using property notation. So you can just do:

def x = mymap.likes

In this case, x will contain the value of mymap['likes'] if it exists, otherwise it will be null.

If the key you are looking for (for example 'likes.key') contains a dot itself, then you can quote the key like so:

def x = mymap.'likes.key'

If the key is stored in a variable, use string interpolation with the property notation like so:

def key = 'likes'
def x = mymap."$key"

If you don't like null, then use you use the elvis operator to assign a default value instead (similar to java.util.Map.getOrDefault):

def x = mymap.someKey ?: 'default value'

Now you know all the secrets.

Remember that groovy maps are still just Java maps with "enhancements", so the rules that apply to Java still apply to groovy. Notably the java.util.Map interface has a note in there that says:

Some map implementations have restrictions on the keys and values they may contain. For example, some implementations prohibit null keys and values, and some have restrictions on the types of their keys. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException. Attempting to query the presence of an ineligible key or value may throw an exception, or it may simply return false; some implementations will exhibit the former behavior and some will exhibit the latter. More generally, attempting an operation on an ineligible key or value whose completion would not result in the insertion of an ineligible element into the map may throw an exception or it may succeed, at the option of the implementation. Such exceptions are marked as "optional" in the specification for this interface.

Basically this says: Not all maps are the same, so make sure you somewhat know the kind of map you are dealing with (internally) as you attempt these operations on them. It shouldn't be a problem for simple stuff because Groovy by default uses java.util.LinkedHashMap.

Solution 5

The reason you get a Null Pointer Exception is because there is no key likesZZZ in your second example. Try:

def mymap = [name:"Gromit", likes:"cheese", id:1234]
def x = mymap.find{ it.key == "likes" }.value
if(x)
    println "x value: ${x}"
Share:
131,701
nonbeing
Author by

nonbeing

Make software or break software, but automate ALL the things!

Updated on March 25, 2021

Comments

  • nonbeing
    nonbeing about 3 years

    I want to find a specific key in a given map. If the key is found, I then want to get the value of that key from the map.

    This is what I managed so far:

    def mymap = [name:"Gromit", likes:"cheese", id:1234]
    
    def x = mymap.find{ it.key == "likes" }
    
    if(x)
        println x.value
    

    This works, the output is "cheese" as expected. Great, but I don't want to do x.value at the end, and I don't want to do if(x). I want x to directly contain the value somehow.

    I can't get the value directly into x like this:

    def mymap = [name:"Gromit", likes:"cheese", id:1234]
    
    def x = mymap.find{ it.key == "likesZZZ" }.value
    
    println x
    

    Because the find closure is null in this case, this results in a Null Pointer Exception. Of course, the above code snippet works when it.key == "likes", but I am not sure that I will always find the target key in the map.

    What is a "Groovier" and safe way to do this on a map:

    • Check if a map has a given key,
    • And if so, get the value of this key