'Optional chaining operator in groovy not working as expected?

How can I concisely set a default value for a value of a closure that is not populated?

Below I thought using the optional chaining operator would prevent throwing an exception, but I still got an exception.

myfunction {
    FOO="myfoo"
    BAZ="mybaz"
}

def myfunction(Closure body = {}) {
    body()

    foo = body?.FOO ?: "defaultFoo"

    // getting exception since BAR isnt defined
    bar = body?.BAR ?: "defaultBar"
    
    println("foo=" + foo + " bar=" + bar)
}


Solution 1:[1]

You can solve it by setting a dedicated Binding on your closure with a map that returns a default value (e.g. null) for non-existing keys.

myfunction {
    FOO="myfoo"
    BAZ="mybaz"
}

def myfunction(Closure body = {}) {
    def binding = new Binding([:].withDefault {}) // <1>
    body.setBinding(binding) // <2>
    body()

    foo = binding.FOO // <3>
    bar = binding.BAR

    println("foo=" + foo + " bar=" + bar)
}

Output:

foo=myfoo bar=null

There are three critical changes to your code.

  1. You define new Binding([:].withDefault {}) object, where [:].withDefault {} is a map that returns a value defined inside the closure for any key that does not exist. In our case, we use an empty closure to return null value, but if we do e.g. [:].withDefault { 1 }, then we create a map that returns 1 for non-existing keys.
  2. Next, you have to call body.setBinding(binding) to make use of this custom Binding instance.
  3. And finally, you read values from the binding object, not the body closure.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Szymon Stepniak