eVoxmusic
eVoxmusic

Reputation: 684

Merge maps with recursive nested maps in Groovy

I would like to know if someone have an easy way to merge 2 deep nested maps together ?

For instance, I would like to get :

[
    "a" : "1",
    "animals" : ["cat" : "blue"]
] + [
    "b" : 2,
    "animals" : ["dog" : "red"]
] == [
    "a" : 1,
    "b" : 2,
    "animals" : [
        "cat" : "blue",
        "dog" : "red"]
]

There is someone having easy solution ?

Upvotes: 18

Views: 7248

Answers (5)

jonhattan
jonhattan

Reputation: 490

I usually use ConfigObject for this. It does deep merge along other goodies.

a= [
    "a" : "1",
    "animals" : ["cat" : "blue"]
]

b= [
    "b" : 2,
    "animals" : ["dog" : "red"]
]

result = (HashMap)new ConfigObject(a).merge(((ConfigObject)b))

Upvotes: 0

Bernie Lenz
Bernie Lenz

Reputation: 2146

Another solution similar to @dmahapatro, but without using metaClass:

Map mergeMaps(Map lhs, Map rhs) {
    rhs.each { k, v ->
        lhs[k] = (lhs[k] in Map ? mergeMaps(lhs[k], v) : v)
    }
    return lhs
}

def m1 = [a: 1, animals: [cat: 'blue']]
def m2 = [b: 2, animals: [dog: 'red']]

mergeMaps(m1, m2)
println(m1)

Upvotes: 1

pczeus
pczeus

Reputation: 7867

This is a very short, concise way of doing it without using the meta-programming:

Map one = ["a" : "1", "animals" : ["cat" : "blue"] ]
Map two = ["b" : "2", "animals" : ["dog" : "red"] ]

Map three = [:]
(one.entrySet() + two.entrySet()).each { entry -> 
    three[entry.key] = three.containsKey(entry.key) ? [:] << three[entry.key] << entry.value : entry.value 
}

println three

Renders the output:

 [a:1, animals:[cat:blue, dog:red], b:2]

Upvotes: 0

dmahapatro
dmahapatro

Reputation: 50245

You can write one for Map using recursion:

Map.metaClass.addNested = { Map rhs ->
    def lhs = delegate
    rhs.each { k, v -> lhs[k] = lhs[k] in Map ? lhs[k].addNested(v) : v }   
    lhs
}

def map1 = [
    "a" : "1",
    "animals" : ["cat" : "blue"]
]

def map2 = [
    "b" : 2,
    "animals" : ["dog" : "red"]
]

assert map1.addNested( map2 ) == [
    a: '1', 
    animals: [cat: 'blue', dog: 'red'], 
    b: 2
]

Upvotes: 20

jalopaba
jalopaba

Reputation: 8119

I had a similar solution as @dmahapatro, but with a method with variable arguments:

def m1 = [a: 1, animals: [cat: 'blue']]
def m2 = [b: 2, animals: [dog: 'red']]

Map merge(Map... maps) {
    Map result

    if (maps.length == 0) {
        result = [:]
    } else if (maps.length == 1) {
        result = maps[0]
    } else {
        result = [:]
        maps.each { map ->
            map.each { k, v ->
                result[k] = result[k] instanceof Map ? merge(result[k], v) : v
            }
        }
    }

    result
}

assert [:] == merge()
assert m1 == merge(m1)
assert [a:1, b:2, animals:[cat:'blue', dog:'red']] == merge(m1, m2)

Upvotes: 12

Related Questions