6
votes

I have a map with keys in dot notation, but I need it as a nested map.

[test.key.one: 'value1', text.key.two: 'value2']

Now the result should be

[
    test: [
        key: [
            one: 'value1',
            two: 'value2'
        ]
    ]
]

And here is my idea of code

def extract(String key, String value) {

    if(key.contains(".")) {
        def (String target, String subKey) = key.split('\\.', 2)
        return ["$target": extract(subKey, value)]
    } else {
        return ["$key": extractType(value)]
    }

}

But I want to know if there is any groovy magic doing this in a closure or witht he help of other goodies to make it simpler.

2

2 Answers

9
votes

There is one handy class: groovy.util.ConfigSlurper

def map = ['test.key.one': 'value1', 'test.key.two': 'value2']
def props = new Properties()
props.putAll(map)
println new ConfigSlurper().parse(props) // [test:[key:[two:value2, one:value1]]]

The only drawback is that it expects java.util.Properties instance, so you need to create one from the map.

1
votes

As Dany's answer doesn't worked for me and I wanted to support Spring Boot like yaml parsing, here's my solution:

Map expandMapKeys(Map source) {
    source.inject([:]) { result, key, value ->
        if (value instanceof Map) value = expandMapKeys(value)
        result + key.tokenize('.').reverse().inject(value) { current, token ->
            [(token): current]
        }
    }
}

assert expandMapKeys([a: 'b']) == [a: 'b']
assert expandMapKeys([a: [b: 'c']]) == [a: [b: 'c']]
assert expandMapKeys(['a.b': 'c']) == [a: [b: 'c']]
assert expandMapKeys([a: ['b.c.d': 'e']]) == [a: [b: [c: [d: 'e']]]]
assert expandMapKeys(['a.b': 'c', 'a.d': 'e']) == [a: [d: 'e']] // not [a: [b: 'c', d: 'e'] !

Notice last assertion: same parent map keys will be overwritten. To fix this, we need to use a deep merge (I found on the internet). Additionally, we'll handle one level of lists, because they're also possible in yaml:

Map expandMapKeys(Map source) {
    source.inject([:]) { result, key, value ->
        if (value instanceof Map) value = expandMapKeys(value)
        if (value instanceof Collection) value = value.collect { it instanceof Map ? expandMapKeys(it) : it }
        merge(result, key.tokenize('.').reverse().inject(value) { current, token ->
            [(token): current]
        })
    }
}

Map merge(Map[] sources) {
    if (!sources) return [:]
    if (sources.length == 1) return sources[0]
    sources.inject([:]) { result, map ->
        map.each { key, value ->
            result[key] = result[key] instanceof Map ? merge(result[key], value) : value
        }
        result
    }
}

assert expandMapKeys([a: 'b']) == [a: 'b']
assert expandMapKeys([a: [b: 'c']]) == [a: [b: 'c']]
assert expandMapKeys(['a.b': 'c']) == [a: [b: 'c']]
assert expandMapKeys([a: ['b.c.d': 'e']]) == [a: [b: [c: [d: 'e']]]]
assert expandMapKeys(['a.b': 'c', 'a.d': 'e']) == [a: [b: 'c', d: 'e']]
assert expandMapKeys([a: ['b.c': 'd'], e: [[f: ['g.h': 'i']], [j: ['k.l': 'm']]]]) == [a: [b: [c: 'd']], e: [[f: [g: [h: 'i']]], [j: [k: [l: 'm']]]]]