Skip to content

Instantly share code, notes, and snippets.

@robhruska
Created January 23, 2013 19:55
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robhruska/4612278 to your computer and use it in GitHub Desktop.
Save robhruska/4612278 to your computer and use it in GitHub Desktop.
Merge nested maps together
class MapMerge {
/**
* Deeply merges the contents of each Map in sources, merging from
* "right to left" and returning the merged Map.
*
* Mimics 'extend()' functions often seen in JavaScript libraries.
* Any specific Map implementations (e.g. TreeMap, LinkedHashMap)
* are not guaranteed to be retained. The ordering of the keys in
* the result Map is not guaranteed. Only nested maps will be
* merged; primitives, objects, and other collection types will be
* overwritten.
*
* The source maps will not be modified.
*/
Map merge(Map[] sources) {
if (sources.length == 0) return [:]
if (sources.length == 1) return sources[0]
sources.inject([:]) { result, source ->
source.each { k, v ->
result[k] = result[k] instanceof Map ? merge(result[k], v) : v
}
result
}
}
}
import groovy.util.GroovyTestCase
class MapMergeTests extends GroovyTestCase {
def instance = new MapMerge()
void testMergeOne () {
def m0 = [
foo: 'bar'
]
assertMapsEqual(m0, instance.merge(m0))
}
void testMerge_Two_NoNesting_NoOverwriting () {
def m0 = [
foo: 'bar'
]
def m1 = [
baz: 'qux'
]
def expected = [
foo: 'bar',
baz: 'qux'
]
assertMapsEqual(expected, instance.merge(m0, m1))
}
void testMerge_Two_NoNesting_WithOverwriting () {
def m0 = [
foo: 'bar'
]
def m1 = [
foo: 'baz'
]
def expected = [
foo: 'baz'
]
assertMapsEqual(expected, instance.merge(m0, m1))
}
void testMerge_Three_NoNesting_WithOverwriting () {
def m0 = [
foo: 'bar'
]
def m1 = [
foo: 'baz'
]
def m2 = [
foo: 'qux'
]
def expected = [
foo: 'qux'
]
assertMapsEqual(expected, instance.merge(m0, m1, m2))
}
void testMerge_Two_NestedOneLevel_NoOverwriting () {
def m0 = [
foo: [
bar: 'baz'
]
]
def m1 = [
qux: [
bar: 'baz'
]
]
def expected = [
foo: [
bar: 'baz'
],
qux: [
bar: 'baz'
]
]
assertMapsEqual(expected, instance.merge(m0, m1))
}
void testMerge_Two_NestedOneLevel_OverwriteNonMaps () {
def m0 = [
foo: 'bar',
baz: [
qux: 'corge'
]
]
def m1 = [
foo: 'waldo',
corge: [
grault: 'garply'
]
]
def expected = [
foo: 'waldo',
baz: [
qux: 'corge'
],
corge: [
grault: 'garply'
]
]
assertMapsEqual(expected, instance.merge(m0, m1))
}
void testMerge_Three_NestedTwoLevels_RatherComplex () {
def m0 = [
foo: 'bar',
baz: [
qux: 'corge',
fred: [
plugh: 'waldo'
]
]
]
def m1 = [
foo: 'thud',
baz: [
quux: 'corge',
fred: [
waldo: 'plugh',
spam: 'ham',
eggs: 'bacon'
],
walrus: [
otter: 'hamster'
]
]
]
def m2 = [
baz: [
fred: [
hippo: 'rhino'
]
],
quiver: 'shatter'
]
def expected = [
baz: [
fred: [
eggs: 'bacon',
hippo: 'rhino',
plugh: 'waldo',
spam: 'ham',
waldo: 'plugh'
],
quux: 'corge',
qux: 'corge',
walrus: [
otter: 'hamster'
]
],
foo: 'thud',
quiver: 'shatter'
]
assertMapsEqual(expected, instance.merge(m0, m1, m2))
}
void testMerge_Two_NoNesting_OverwritesList () {
def m0 = [
foo: [
'bar',
'baz'
]
]
def m1 = [
foo: [
'qux',
'quux'
]
]
def expected = [
foo: [
'qux',
'quux'
]
]
assertMapsEqual(expected, instance.merge(m0, m1))
}
// A couple rather crude map equality testers.
private void assertMapsEqual(expected, actual) {
compareMapsWithAssertions(expected, actual)
compareMapsWithAssertions(actual, expected)
}
private void compareMapsWithAssertions(expected, actual) {
expected.each { k, v ->
if (v instanceof Map) {
compareMapsWithAssertions(expected[k], actual[k])
} else {
assert v == actual[k]
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment