Skip to content
Browse files

combine filter: allow merging dict and list

  • Loading branch information...
Maxime de Roucy
Maxime de Roucy committed Jun 10, 2019
1 parent 4fa93d5 commit c3cce39ae273de0389375502b374d4d083b7f1f7
Showing with 55 additions and 12 deletions.
  1. +15 −4 docs/docsite/rst/user_guide/playbooks_filters.rst
  2. +9 −7 lib/ansible/plugins/filter/
  3. +31 −1 lib/ansible/utils/
@@ -864,18 +864,29 @@ The resulting hash would be::

{'a':1, 'b':3}

The filter also accepts an optional `recursive=True` parameter to not
only override keys in the first hash, but also recurse into nested
hashes and merge their keys too
The filter also accepts an optional `recursive` parameter.
`recursive='merge_hash'` (or `recursive=True`) not only override keys in the
first hash, but also recurse into nested hashes and merge their keys too.

.. code-block:: jinja

{{ {'a':{'foo':1, 'bar':2}, 'b':2} | combine({'a':{'bar':3, 'baz':4}}, recursive=True) }}
{{ {'a':{'foo':1, 'bar':2}, 'b':2} | combine({'a':{'bar':3, 'baz':4}}, reduce='merge_hash') }}

This would result in::

{'a':{'foo':1, 'bar':3, 'baz':4}, 'b':2}

`recursive='merge_hash_and_array'` has the same effect as
`recursive='merge_hash'` but also merge lists.

.. code-block:: jinja

{{ {'a': ['b', 'c']} | combine({'a': ['c']}, reduce='merge_hash_and_array') }}

This would result in::

{'a': ['b', 'c', 'c']}

The filter can also take multiple arguments to merge::

{{ a | combine(b, c, d) }}
@@ -53,7 +53,7 @@
from ansible.utils.encrypt import passlib_or_crypt
from ansible.utils.hashing import md5s, checksum_s
from ansible.utils.unicode import unicode_wrap
from ansible.utils.vars import merge_hash
from ansible.utils.vars import merge_hash, merge_hash_and_array

display = Display()

@@ -293,10 +293,7 @@ def mandatory(a):
return a

def combine(*terms, **kwargs):
recursive = kwargs.get('recursive', False)
if len(kwargs) > 1 or (len(kwargs) == 1 and 'recursive' not in kwargs):
raise AnsibleFilterError("'recursive' is the only valid keyword argument")
def combine(*terms, recursive=None):

dicts = []
for t in terms:
@@ -305,12 +302,17 @@ def combine(*terms, **kwargs):
elif isinstance(t, list):
dicts.append(combine(*t, **kwargs))
dicts.append(combine(*t, recursive=recursive))
raise AnsibleFilterError("|combine expects dictionaries, got " + repr(t))

if recursive:
return reduce(merge_hash, dicts)
if recursive == 'merge_hash' or recursive is True:
return reduce(merge_hash, dicts)
elif recursive == 'merge_hash_and_array':
return reduce(merge_hash_and_array, dicts)
raise AnsibleFilterError("'recursive' unknown value '%s'"%recursive)
return dict(itertools.chain(*map(iteritems, dicts)))

@@ -31,7 +31,7 @@
from ansible.errors import AnsibleError, AnsibleOptionsError
from ansible.module_utils.six import iteritems, string_types
from ansible.module_utils._text import to_native, to_text
from ansible.module_utils.common._collections_compat import MutableMapping
from ansible.module_utils.common._collections_compat import MutableMapping, MutableSequence
from ansible.parsing.splitter import parse_kv

@@ -119,6 +119,36 @@ def merge_hash(a, b):

return result

def merge_hash_and_array(a, b):
Recursively merges hash b into a so that keys from b take precedence over keys from a

_validate_mutable_mappings(a, b)

# if a is empty or equal to b, return b
if a == {} or a == b:
return b.copy()

# if b is empty the below unfolds quickly
result = a.copy()

# next, iterate over b keys and values
for k, v in iteritems(b):
# if there's already such key in a
# and that key contains a MutableMapping
k_in_result = k in result
if k_in_result and isinstance(result[k], MutableMapping) and isinstance(v, MutableMapping):
# merge those dicts recursively
result[k] = merge_hash_and_array(result[k], v)
elif k_in_result and isinstance(result[k], MutableSequence) and isinstance(v, MutableSequence):
result[k] += v
# otherwise, just copy the value from b to a
result[k] = v

return result

def load_extra_vars(loader):
extra_vars = {}

0 comments on commit c3cce39

Please sign in to comment.
You can’t perform that action at this time.