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

  1. Normalize both sides to dicts before diffing: treat None as {} (absent == empty) if that matches your semantics
  2. Parse JSON strings with module.from_json() or json.loads() before calling recursive_diff
  3. 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

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


AI-assisted analysis of ansible/ansible@9cf16a4aca (2026-08-15). Data as JSON: /api/errors/c706117a427e93b4. Report an issue: GitHub.