ansible/ansible · error · TypeError
Unable to diff 'dict1' %s and 'dict2' %s. Both must be a dic
Error message
Unable to diff 'dict1' %s and 'dict2' %s. Both must be a dictionary.
What it means
Raised by ansible.module_utils.common.dict_transformations.recursive_diff() with TypeError when either dict1 or dict2 is not a Mapping (dict-like). The function recursively compares nested dictionaries and returns None for equal inputs or a (left, right) tuple of differences; the type check happens up front on both arguments.
Source
Thrown at lib/ansible/module_utils/common/dict_transformations.py:136
if k in result and isinstance(result[k], dict):
result[k] = dict_merge(result[k], v)
else:
result[k] = deepcopy(v)
return result
def recursive_diff(dict1, dict2):
"""Recursively diff two dictionaries
Raises ``TypeError`` for incorrect argument type.
:arg dict1: Dictionary to compare against.
:arg dict2: Dictionary to compare with ``dict1``.
:return: Tuple of dictionaries of differences or ``None`` if there are no differences.
"""
if not all((isinstance(item, MutableMapping) for item in (dict1, dict2))):
raise TypeError("Unable to diff 'dict1' %s and 'dict2' %s. "
"Both must be a dictionary." % (type(dict1), type(dict2)))
left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)
right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)
for k in (set(dict1.keys()) & set(dict2.keys())):
if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
result = recursive_diff(dict1[k], dict2[k])
if result:
left[k] = result[0]
right[k] = result[1]
elif dict1[k] != dict2[k]:
left[k] = dict1[k]
right[k] = dict2[k]
if left or right:
return left, right
return None
View on GitHub (pinned to 9cf16a4aca)
Solutions
- Normalize both sides to dicts before diffing: treat None as {} (absent == empty) if that matches your semantics
- Parse JSON strings with module.from_json() or json.loads() before calling recursive_diff
- If comparing non-dict structures, write a different comparison instead of forcing recursive_diff
Example fix
# before
diff = recursive_diff(existing_config, new_config) # existing_config may be None
# after
existing = existing_config or {}
new = new_config or {}
diff = recursive_diff(existing, new) Defensive patterns
Strategy: type-guard
Validate before calling
from collections.abc import Mapping
if not isinstance(dict1, Mapping):
dict1 = {} if dict1 is None else module.fail_json(msg='dict1 must be a dict')
if not isinstance(dict2, Mapping):
dict2 = {} if dict2 is None else module.fail_json(msg='dict2 must be a dict') Type guard
from collections.abc import Mapping
def is_diffable(*objs) -> bool:
return all(isinstance(o, Mapping) for o in objs) Try / catch
try:
result = recursive_diff(dict1, dict2)
except TypeError as e:
module.fail_json(msg=f'Cannot diff: {to_native(e)} — both arguments must be dictionaries') Prevention
- Treat None (absent state) as {} before diffing
- Parse JSON strings with module.from_json() before recursive_diff
- Type-check inputs at module parameter processing time, not at diff time
When it happens
Trigger: recursive_diff(a, b) where one side is None (e.g. a value absent in module params), a list, a string, or a Jinja-templated string that was never converted to a dict.
Common situations: Module code diffing before/after state where one side is None because the object did not previously exist; passing a JSON string instead of from_json-parsed data; comparing lists captured from an API.
Related errors
- Value '{1}' in the sub parameter field '{0}' must be a {2},
- Unknown parameter type: %s
- Value of unknown type: %s, %s
- Failed to duplicate token
- Failed to open process {0} with access {1}
AI-assisted analysis of ansible/ansible@9cf16a4aca (2026-08-15).
Data as JSON: /api/errors/c706117a427e93b4.
Report an issue: GitHub.