Grant-bobo
Grant-bobo

Reputation: 41

Is any error in my code in using evaluate in groovy?

I'm setting value to a nested Map, the keys are all exist, here is my code:

def map = [a1:[a2:[a3:'a123']], b1:[b2:[b3_1:'b234', b3_2:'b345']], d1:'d1']
def name2 = "b1:::b2:::b3_1"
def data = "test"
def separator = ":::"

def names = name2.split(separator)

setValue(map, names, data)

def setValue(def map, def keys, def data) {
    tmpMap = map
    String str = 'map' 
    for (i = 0; i < keys.size(); i++) {
        str = str.concat('.'+ keys[i])
    }
    str = str.concat(" = '" + data + "'")

    evaluate(str)
}

the str content will be "map.b1.b2.b3_1 = 'test'" I can manually run it and the map content is changed, but when I use evaluate, there has error of "java.lang.NullPointerException: Cannot get property 'b2' on null object", would you please tell me what's wrong with the code, thanks a lot.

Upvotes: 0

Views: 113

Answers (2)

daggett
daggett

Reputation: 28564

recurse approach:

def setValue(def map, List keys, def data) {
    return keys.size()>1 ? 
        setValue(map[keys[0]],keys.subList(1,keys.size()),data) : 
            map.put(keys[0],data)
}

Upvotes: 0

tim_yates
tim_yates

Reputation: 171114

What you're trying to do is hard...

One method would be to use Eval.x, and pass in the map as the x:

def setValue(Map map, String[] keys, String data) {
    def command = "x.${keys.join('.')} = '$data'"
    Eval.x(map, command)
}

So here, command would be: x.b1.b2.b3_1 = 'test' and we evaluate it with x being the value of map

Another (more ugly) method is to use inject to walk the map until the last key, and then set the value of the last key of this returned object:

def setValue(Map map, String[] keys, String data) {
    keys[0..-2].inject(map) { a, b -> a."$b" }."${keys[-1]}" = data
}

You should be wary of evaluating code willy nilly though....

For example, the following input you may receive:

def name2 = "val = { -> println \"${System.properties.'user.name'}\" }.call(); def a"

Evaluates to the expression (for me):

x.val = { -> println "tim" }.call(); def a = 'test'

Which then prints the user name... You can see how this could be adapted to download a file from the internet and put it on someone's machine...

If you can... You should avoid this... Or at least have stringent whitelists of what is allowed

Upvotes: 1

Related Questions