{"record":{"id":"c706117a427e93b4","repo":"ansible/ansible","slug":"unable-to-diff-dict1-s-and-dict2-s-both-mus","errorCode":null,"errorMessage":"Unable to diff 'dict1' %s and 'dict2' %s. Both must be a dictionary.","messagePattern":"Unable to diff 'dict1' (.+?) and 'dict2' (.+?)\\. Both must be a dictionary\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"lib/ansible/module_utils/common/dict_transformations.py","lineNumber":136,"sourceCode":"        if k in result and isinstance(result[k], dict):\n            result[k] = dict_merge(result[k], v)\n        else:\n            result[k] = deepcopy(v)\n    return result\n\n\ndef recursive_diff(dict1, dict2):\n    \"\"\"Recursively diff two dictionaries\n\n    Raises ``TypeError`` for incorrect argument type.\n\n    :arg dict1: Dictionary to compare against.\n    :arg dict2: Dictionary to compare with ``dict1``.\n    :return: Tuple of dictionaries of differences or ``None`` if there are no differences.\n    \"\"\"\n\n    if not all((isinstance(item, MutableMapping) for item in (dict1, dict2))):\n        raise TypeError(\"Unable to diff 'dict1' %s and 'dict2' %s. \"\n                        \"Both must be a dictionary.\" % (type(dict1), type(dict2)))\n\n    left = dict((k, v) for (k, v) in dict1.items() if k not in dict2)\n    right = dict((k, v) for (k, v) in dict2.items() if k not in dict1)\n    for k in (set(dict1.keys()) & set(dict2.keys())):\n        if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):\n            result = recursive_diff(dict1[k], dict2[k])\n            if result:\n                left[k] = result[0]\n                right[k] = result[1]\n        elif dict1[k] != dict2[k]:\n            left[k] = dict1[k]\n            right[k] = dict2[k]\n    if left or right:\n        return left, right\n    return None\n","sourceCodeStart":118,"sourceCodeEnd":153,"githubUrl":"https://github.com/ansible/ansible/blob/9cf16a4aca7898481c257f1e17ad28d0b67b1f85/lib/ansible/module_utils/common/dict_transformations.py#L118-L153","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\ndiff = recursive_diff(existing_config, new_config)  # existing_config may be None\n\n# after\nexisting = existing_config or {}\nnew = new_config or {}\ndiff = recursive_diff(existing, new)","handlingStrategy":"type-guard","validationCode":"from collections.abc import Mapping\n\nif not isinstance(dict1, Mapping):\n    dict1 = {} if dict1 is None else module.fail_json(msg='dict1 must be a dict')\nif not isinstance(dict2, Mapping):\n    dict2 = {} if dict2 is None else module.fail_json(msg='dict2 must be a dict')","typeGuard":"from collections.abc import Mapping\n\ndef is_diffable(*objs) -> bool:\n    return all(isinstance(o, Mapping) for o in objs)","tryCatchPattern":"try:\n    result = recursive_diff(dict1, dict2)\nexcept TypeError as e:\n    module.fail_json(msg=f'Cannot diff: {to_native(e)} — both arguments must be dictionaries')","preventionTips":["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"],"tags":["dict","diff","type-error","ansible-module-utils"],"backgroundTag":null,"analyzedSha":"9cf16a4aca7898481c257f1e17ad28d0b67b1f85","analyzedAt":"2026-08-15T00:15:47.100Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}