diff --git a/CHANGELOG.md b/CHANGELOG.md index 9282c421..cf413292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # DeepDiff Change log - Unreleased + - Preserve original string key casing in reported paths when `ignore_string_case=True`, so path exclusions can use the input keys (#515). - Fixed missing type changes between equal-comparing items inside ordered iterables, e.g. `DeepDiff([2], [2.0])` now reports the `int` → `float` change like `DeepDiff(2, 2.0)` and `DeepDiff({'a': 2}, {'a': 2.0})` already do (issue #605). - v9-1-0 diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 4c7a4e5c..e3a26282 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -813,13 +813,15 @@ def _diff_dict( continue parents_ids_added = add_to_frozen_set(parents_ids, item_id) + # Case normalization is for matching, not for paths into the input objects. + preserve_case = self.ignore_string_case and isinstance(key1, str) and isinstance(key2, str) # Go one level deeper next_level = level.branch_deeper( t1[key1], t2[key2], child_relationship_class=rel_class, - child_relationship_param=key, - child_relationship_param2=key, + child_relationship_param=key1 if preserve_case else key, + child_relationship_param2=key2 if preserve_case else key, ) self._diff(next_level, parents_ids_added, local_tree=local_tree) diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index cfeda768..6ea5a943 100755 --- a/tests/test_diff_text.py +++ b/tests/test_diff_text.py @@ -369,6 +369,36 @@ def test_string_dict_key_ignore_case(self): result = {} assert result == ddiff + def test_ignore_case_preserves_original_dictionary_paths(self): + t1 = {'User': {'AboutMe': 1}} + t2 = {'user': {'aboutme': 2}} + diff = DeepDiff(t1, t2, ignore_string_case=True) + assert diff == {'values_changed': { + "root['User']['AboutMe']": {'old_value': 1, 'new_value': 2} + }} + tree = DeepDiff(t1, t2, ignore_string_case=True, view='tree') + change = next(iter(tree['values_changed'])) + assert change.path() == "root['User']['AboutMe']" + assert change.path(use_t2=True) == "root['user']['aboutme']" + + @pytest.mark.parametrize('path_option', ['exclude_paths', 'exclude_regex_paths']) + def test_ignore_case_excludes_original_dictionary_path(self, path_option): + t1 = {'User': {'AboutMe': 1}} + t2 = {'User': {'AboutMe': 2}} + path = "root['User']['AboutMe']" + if path_option == 'exclude_regex_paths': + path = r"root\['User'\]\['AboutMe'\]$" + assert not DeepDiff(t1, t2, ignore_string_case=True, **{path_option: [path]}) + + def test_ignore_case_preserves_paths_for_nested_additions_and_removals(self): + t1 = {'User': {'Keep': 1, 'Removed': 2}} + t2 = {'user': {'keep': 1, 'Added': 3}} + diff = DeepDiff(t1, t2, ignore_string_case=True, threshold_to_diff_deeper=0) + assert diff == { + 'dictionary_item_added': ["root['User']['Added']"], + 'dictionary_item_removed': ["root['User']['Removed']"], + } + def test_string_list_ignore_case(self): t1 = ['AboutMe', 'ALIAS'] t2 = ['aboutme', 'alias']