The jenkins-pipeline-library contains the helper
MapUtils.groovy
which is heavily used to integrate custom changes into the configuration
map.
MapUtils.groovy
will merge 2-n Maps recursively together and returns
the merged result without modifying the original maps.
The MapUtils tool merges maps using the following rules.
Keys that are unique in each map to merge are just "merged" together.
map1 = ["property_map1": "value_map1"]
map2 = ["property_map2": "value_map2"]
result1 = MapUtils.merge(map1, map2)
/*
result1 = [
"property_map1" : "value_map1",
"property_map2" : "value_map2"
]
*/
property_map2
did not exist inmap1
so it was just added.
When items in the map can not be merged they are overwritten by the map "to the right".
map1 = ["int": 1, "string": "1", "list": [1], "map": ["key": "map1Value"]]
map2 = ["int": 2, "string": "2", "list": 2, "map": 2]
result1 = MapUtils.merge(map1, map2)
result2 = MapUtils.merge(map2, map1)
/*
result1 = [ "int" : 2, "string" : "2", "list" : 2, "map" : 2 ]
result2 = [ "int" : 1, "string" : "1", "list" : [1], "map" : [ "key" : "map1Value" ] ]
*/
result1
: Whenmap2
was merged intomap1
all keys were present in both maps, but the datatypes were not mergeable so the items frommap2
replaced the items frommap1
result2
: This is the opposite toresult1
but heremap1
was merged intomap2
so the values frommap1
replaced the items frommap2
The following datatypes support merging:
java.util.List
java.util.Map
So when the datatype is one of the above ones and the datatype is equal for an item key the item is merged.
map1 = ["list": [1, 0]]
map2 = ["list": [2, 0]]
result1 = MapUtils.merge(map1, map2)
result2 = MapUtils.merge(map2, map1)
/*
result1 = [ "list" : [ 1, 0, 2 ] ]
result2 = [ "list" : [ 2, 0, 1 ] ]
*/
As you can see both results contain the same elements in different
order, depending if you merge map2
into map1
or the other way
around.
Maps will be merged recursively following the rules from above for Adding, Overwriting and Merging items.
map1 = [lvl1: [map1Item: "map1Value", list: [1], int: 1, str: "1", "lvl2": [map1: "1"]]]
map2 = [lvl1: [map2Item: "map2Value", list: [2], int: 2, str: "2", "lvl2": [map2: "2"]]]
result1 = MapUtils.merge(map1, map2)
result2 = MapUtils.merge(map2, map1)
/*
result1 = [lvl1:[map1Item: "map1Value", list: [1, 2], int: 2, str: "2", lvl2 : [map1: "1", map2: "2"], map2Item: "map2Value" ]]
result2 = [lvl1:[map2Item: "map2Value", list: [2, 1], int: 1, str: "1", lvl2 : [map2: "2", map1: "1"], map1Item: "map1Value" ]]
*/
- The
lvl1
item frommap2
was integrated into thelvl1
item frommap1
- The
list
frommap1
andmap2
was merged because the items from both maps were of the typejava.util.List
- The items
int
andstr
frommap2
replaced the corresponding items frommap1
- The map item
lvl2
frommap2
merged with the corresponding item frommap1
- The item
map2Item
was added
The result2
is like the result1
but items from map1
had precedence
before items from map2
.
The passed maps will be processed from the left to the right which means that the second Map will overwrite the first Map.
Map map1 = [ "source" : "map1", "str1" : "hello map1" ]
Map map2 = [ "source" : "map2", "str2" : "hello map2" ]
Map map3 = [ "source" : "map3", "str3" : "hello map3" ]
Map result1 = MapUtils.merge(map1, map2)
Map result2 = MapUtils.merge(map1, map2, map3)
/*
result1 = [
"source" : "map1",
"map1" : "hello map1",
"map2" : "hello map2"
]
result2 = [
"source" : "map1",
"map1" : "hello map1",
"map2" : "hello map2",
"map2" : "hello map3"
]
*/
For result1 the merge was applied as follows
map1
andmap2
both containedsource
andsource
is a simple data type (string
) so the value frommap2
replaced the value ofmap1
str2
frommap2
was simply added to the result because is did not exist inmap1
For result2 the merge was applied as follows
map1
andmap2
both containedsource
andsource
is a simple data type (string
) so the value frommap2
replaced the value ofmap1
str2
frommap2
was simply added to the result because is did not exist inmap1
map3
also containedsource
andsource
is a simple data type (string
) so the value from the merge ofmap1
andmap2
were replaced by the value ofmap3
str3
frommap3
was simply added to the result because is did not exist inmap1
normap2
With version 1.6.0 of the jenkins pipeline library merge modes were introduced to provide more control about the merge behavior.
The existing, default merge mode is MapMergeMode.MERGE
.
The corresponding config constant is ConfigConstants.MAP_MERGE_MODE
.
The used map merge mode is retrieved in the following order:
- use the
MapMergeMode
from the existing / "left" map, if defined - use the
MapMergeMode
from the incoming / "right" map, if defined - use the
MapMergeMode.MERGE
as default
Example definition:
import io.wcm.devops.jenkins.pipeline.utils.ConfigConstants
import io.wcm.devops.jenkins.pipeline.utils.maps.MapMergeMode
Map config = [
(ConfigConstants.MAP_MERGE_MODE) : MapMergeMode.MERGE
]
This is the default merge mode and follows the merge rules.
SKIP the merge, keep the existing value, do not overwrite items in the "left" map with items in the "right" map.
map1 = [int: 1, string: "1", list: [1], map: [map1Key: "map1Value"], map1Item: "map1Item", (MAP_MERGE_MODE): MapMergeMode.SKIP]
map2 = [int: 2, string: "2", list: [2], map: [map2Key: "map2Value"], map2Item: "map2Item" ]
result1 = MapUtils.merge(map1, map2)
result2 = MapUtils.merge(map2, map1)
/*
result1 = [int: 1, string: "1", list: [1], map: [map1Key: "map1Value"], map1Item: "map1Item", mapMergeMode: "SKIP", map2Item: "map2Item"]
result2 = [int: 2, string: "2", list: [2], map: [map2Key: "map2Value"], map2Item: "map2Item", map1Item: "map1Item", mapMergeMode: "SKIP"]
*/
For result1 the merge was applied as follows:
- each value for a key present in
map1
andmap2
was taken frommap1
- so themap
and thelist
item were not merged! - All other "non-overlapping" items were added (e.g.
map2Item
)
For result2 the merge was applied as follows:
- each value for a key present in
map1
andmap2
was taken frommap2
- so themap
and thelist
item were not merged! - All other "non-overlapping" items were added (e.g.
map1Item
andmapMergeMode
)
This mode will replace all existing / overlapping keys in the map to the
left with the items from the map to the right. So this mode is the
opposite of MapMergeMode.SKIP
.
map1 = [int: 1, string: "1", list: [1], map: [map1Key: "map1Value"], map1Item: "map1Item", (MAP_MERGE_MODE): MapMergeMode.REPLACE]
map2 = [int: 2, string: "2", list: [2], map: [map2Key: "map2Value"], map2Item: "map2Item"]
result1 = MapUtils.merge(map1, map2)
result2 = MapUtils.merge(map2, map1)
/*
result1 = [int: 2, string: "2", list: [2], map: [ map2Key: "map2Value"], map1Item: "map1Item", mapMergeMode: "REPLACE", map2Item: "map2Item"]
result2 = [int: 1, string: "1", list: [1], map: [ map1Key: "map1Value"], map2Item: "map2Item", map1Item: "map1Item" , mapMergeMode: "REPLACE"]
*/
For result1 the merge was applied as follows:
- each value for a key present in
map1
andmap2
was taken frommap2
- so themap
and thelist
item were not merged! - All other "non-overlapping" items were added (e.g.
map2Item
)
For result2 the merge was applied as follows:
- each value for a key present in
map1
andmap2
was taken frommap1
- so themap
and thelist
item were not merged! - All other "non-overlapping" items were added (e.g.
map1Item
andmapMergeMode
)
The MapMergeMode can only be defined in items of the type
java.util.Map
and they are only valid for the current map item /
level.
map1 = [merge: [key_merge: ["map1"]], skip: [key_skip: ["map1"], (MAP_MERGE_MODE): MapMergeMode.SKIP], replace: [key_replace: ["map1"], (MAP_MERGE_MODE): MapMergeMode.REPLACE]]
map2 = [merge: [key_merge: ["map2"]], skip: [key_skip: ["map2"]], replace: [key_replace: ["map2"]]]
result1 = MapUtils.merge(map1, map2)
result2 = MapUtils.merge(map2, map1)
/*
result1 = [merge: [key_merge: ["map1", "map2"]], skip: [key_skip:["map1"], mapMergeMode:SKIP], replace: [key_replace: ["map2"], mapMergeMode: "REPLACE"]]
result2 = [merge: [key_merge: ["map2", "map1"]], skip: [key_skip:["map2"], mapMergeMode:SKIP], replace: [key_replace: ["map1"], mapMergeMode: "REPLACE"]]
*/
- The
merge
item used the default merge mode (MapMergeMode.MERGE
) - The
skip
item used the merge mode (MapMergeMode.SKIP
)- In
result1
the values from the incoming mapmap2
were skipped so the values frommap1
were retained - In
result2
the values from the incoming mapmap1
were skipped so the values frommap2
were retained
- In
- The
replace
item used the merge mode (MapMergeMode.REPLACE
)- In
result1
the values from the incoming mapmap2
were used so the values frommap1
were replaced - In
result2
the values from the incoming mapmap1
were used so the values frommap2
were replaced
- In