Skip to content

compare

compare

Data Comparison Utilities

This module provides utilities for comparing complex data structures like dictionaries and lists, with detailed difference reporting.

Functions:

Name Description
compare_dicts

Recursively compare two dictionaries and return differences

Example

dict1 = {"user": {"name": "John", "age": 30}, "items": [1, 2, 3]} dict2 = {"user": {"name": "Jane", "age": 30}, "items": [1, 2]} differences = compare_dicts(dict1, dict2) for diff in differences: ... print(f"{diff['key']}: {diff['message']}")

compare_dicts

compare_dicts(
    dict1: object, dict2: object, path: str = ""
) -> list[dict[str, str]]

Recursively compare two dictionaries (and lists) and return differences.

This function performs a deep comparison of two data structures, supporting nested dictionaries and lists. It returns a detailed list of all differences found between the structures.

Parameters:

Name Type Description Default
dict1 Any

First data structure to compare (typically dict or list)

required
dict2 Any

Second data structure to compare (typically dict or list)

required
path str

Current path in the data structure (used for recursion)

''

Returns:

Type Description
list[dict[str, str]]

list[dict[str, str]]: list of difference dictionaries, each containing: - 'key': The path to the difference - 'message': Description of the difference

Example

data1 = { ... "user": {"name": "John", "age": 30}, ... "active": True, ... "tags": ["admin", "user"] ... } data2 = { ... "user": {"name": "Jane", "age": 30, "email": "jane@example.com"}, ... "tags": ["user"] ... } differences = compare_dicts(data1, data2) for diff in differences: ... print(f"{diff['key']}: {diff['message']}") user.name: Value mismatch at 'user.name': John != Jane user.email: found in second dict but not in first. active: found in first dict but not in second. tags[0]: Extra item in first list at 'tags[0]': admin

Note
  • Handles nested dictionaries and lists recursively
  • Identifies missing keys in either dictionary
  • Detects value mismatches at any level
  • Reports extra items in lists with their positions
  • Ignores None values when reporting missing keys
Source code in src/crew_dcs/utils/compare.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def compare_dicts(dict1: object, dict2: object, path: str = "") -> list[dict[str, str]]:  # noqa: C901
    """
    Recursively compare two dictionaries (and lists) and return differences.

    This function performs a deep comparison of two data structures, supporting
    nested dictionaries and lists. It returns a detailed list of all differences
    found between the structures.

    Args:
        dict1 (Any): First data structure to compare (typically dict or list)
        dict2 (Any): Second data structure to compare (typically dict or list)
        path (str): Current path in the data structure (used for recursion)

    Returns:
        list[dict[str, str]]: list of difference dictionaries, each containing:
            - 'key': The path to the difference
            - 'message': Description of the difference

    Example:
        >>> data1 = {
        ...     "user": {"name": "John", "age": 30},
        ...     "active": True,
        ...     "tags": ["admin", "user"]
        ... }
        >>> data2 = {
        ...     "user": {"name": "Jane", "age": 30, "email": "jane@example.com"},
        ...     "tags": ["user"]
        ... }
        >>> differences = compare_dicts(data1, data2)
        >>> for diff in differences:
        ...     print(f"{diff['key']}: {diff['message']}")
        user.name: Value mismatch at 'user.name': John != Jane
        user.email: found in second dict but not in first.
        active: found in first dict but not in second.
        tags[0]: Extra item in first list at 'tags[0]': admin

    Note:
        - Handles nested dictionaries and lists recursively
        - Identifies missing keys in either dictionary
        - Detects value mismatches at any level
        - Reports extra items in lists with their positions
        - Ignores None values when reporting missing keys
    """
    diff_ls = []

    if isinstance(dict1, dict) and isinstance(dict2, dict):
        # Compare dictionary keys and values
        for key in dict1:
            if key not in dict2:
                if dict1[key] is not None:
                    diff_ls.append(
                        {
                            "key": path + str(key),
                            "message": "found in first dict but not in second.",
                        }
                    )
            else:
                # Recursively compare nested values
                nested_diffs = compare_dicts(dict1[key], dict2[key], path + f"{key}.")
                diff_ls.extend(nested_diffs)

        # Check for keys in second dict that aren't in first
        for key in dict2:
            if key not in dict1 and dict2[key] is not None:
                diff_ls.append(
                    {
                        "key": path + str(key),
                        "message": "found in second dict but not in first.",
                    }
                )

    elif isinstance(dict1, list) and isinstance(dict2, list):
        # Compare lists element by element
        min_len = min(len(dict1), len(dict2))

        # Compare common elements
        for i in range(min_len):
            nested_diffs = compare_dicts(dict1[i], dict2[i], path + f"[{i}].")
            diff_ls.extend(nested_diffs)

        # Check for extra elements in first list
        if len(dict1) > len(dict2):
            for i in range(min_len, len(dict1)):
                diff_ls.append(
                    {
                        "key": f"{path}[{i}]",
                        "message": f"Extra item in first list at '{path}[{i}]': {dict1[i]}",
                    }
                )

        # Check for extra elements in second list
        elif len(dict2) > len(dict1):
            for i in range(min_len, len(dict2)):
                diff_ls.append(
                    {
                        "key": f"{path}[{i}]",
                        "message": f"Extra item in second list at '{path}[{i}]': {dict2[i]}",
                    }
                )
    else:
        # Compare primitive values
        if dict1 != dict2:
            # Remove trailing dot from path for cleaner output
            clean_path = path[:-1] if path.endswith(".") else path
            diff_ls.append(
                {
                    "key": clean_path,
                    "message": f"Value mismatch at '{clean_path}': {dict1} != {dict2}",
                }
            )

    return diff_ls