Search code examples
groovy

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

  • 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.