Created
January 23, 2013 19:55
-
-
Save robhruska/4612278 to your computer and use it in GitHub Desktop.
Merge nested maps together
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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