invenit
invenit

Reputation: 424

Why accessing to groovy map through variable in parentheses always returns null?

Brief

I need null-safe access to map/property by dynamic name of the key. Map can be null itself. I've came up with map?.(keyName) but wonder why it works not as expected and returns always null

Code sample

def map = [key: 'value']
def keyName = 'key'

map.each({ key, value -> println("[${key.class}]: [${value.class}]")})
println("keyName class is [${keyName.class}]")

println(map?.(keyName))                        // null  <--------- My question

// Alternatives that work well
println(map?.(keyName as String))              // value
println(map?.(keyName as java.lang.String))    // value
println(map?.get(keyName))                     // value

​

Output

[class java.lang.String]: [class java.lang.String]
keyName class is [class java.lang.String]
null
value
value
value

Question

If both keyName and map keys are of type java.lang.String why map?.(keyName) does not return value? Why map?.(keyName as String) returns? ​

Links

Checked in Groovy web console

Upvotes: 3

Views: 4883

Answers (3)

Roman Badiornyi
Roman Badiornyi

Reputation: 1539

As @Vampire pointed out

The dot-notation for retrieval only works for String keys

but in my case I had a need to set key dynamically and it was not string key. So solution I end up was to iterate through out the map until I reach my key section:

def SetMapEntry(String key, String value, Map map) {
  def entry = map
  def keyRoute = key.split('\\.')
  def entryLength = keyRoute.size()
  
  key.split('\\.').eachWithIndex{it,index->
    if (entryLength - 1 == index) {
      entry.(it as String) = value
    }
    else {
      entry = entry?.get(it)
    }
  }
}

Upvotes: 0

Vampire
Vampire

Reputation: 38639

The parenthese just control precedence of evaluation. With (keyName), there is nothing to evaluate in the parentheses, so the parentheses are meaningless and map?.(keyName) is identical to map?.keyName which means map?.get('keyName').

With map?.(keyName as String) there is something to evaluate in the parentheses, so it evaluated first and is resolved to map?.'key' which means map?.get('key').

If you want to use a dynamic key value, you have to make sure it is evaluated by some expression. Various choices (some you already found) are:

  • map?.(keyName as String)
  • map?.get(keyName)
  • map?."$keyName"
  • map[keyName]

While the last option is not null-safer regarding the map of course. I personally prefer the third method.

You might wonder why def map = [(keyName): 'value'] works for using the value of keyName as key, but map?.(keyName) does not work for using the value of keyName for retrieval, but the reason is simple. The dot-notation for retrieval only works for String keys. The defining of the map with the parentheses works for any object type. So you can use any Object as key when defining a map with key in parentheses, but you can only ever extract objects from a map with String key with the dot-notation, hence it is ok that the semantics are different here regarding the parentheses.

Upvotes: 6

Will
Will

Reputation: 14519

Groovy is resolving map?.(keyName) to map?.get('keyName'). Using map?.keyName yields the same result:

def map = [key: 'value', keyName: 'gotcha']
def keyName = 'key'

result = map?.(keyName)

assert result == 'gotcha'
assert map?.keyName == 'gotcha'

Upvotes: 4

Related Questions