Skip to content

Commit a3c0684

Browse files
committed
Fix for diffing using iterable_compare_func with nested objects.
This commit addresses two issues. First ensuring that the diff indexes for moved items are always relative to t2 (except for removed) to stay consistent with the rest of the diff types. Second, when replaying moved items ensure that the new values is replaced after adding the items. Since the moved items already have any nested items inside of them, there is no need to replay those nested added items (it was causing items to get double added).
1 parent 81341e2 commit a3c0684

File tree

4 files changed

+86
-9
lines changed

4 files changed

+86
-9
lines changed

deepdiff/delta.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,21 @@ def _del_elem(self, parent, parent_to_obj_elem, parent_to_obj_action,
263263
def _do_iterable_item_added(self):
264264
iterable_item_added = self.diff.get('iterable_item_added', {})
265265
iterable_item_moved = self.diff.get('iterable_item_moved')
266+
267+
# First we need to create a placeholder for moved items.
268+
# This will then get replaced below after we go through added items.
269+
# Without this items can get double added because moved store the new_value and does not need item_added replayed
266270
if iterable_item_moved:
267-
added_dict = {v["new_path"]: v["value"] for k, v in iterable_item_moved.items()}
271+
added_dict = {v["new_path"]: None for k, v in iterable_item_moved.items()}
268272
iterable_item_added.update(added_dict)
269273

270274
if iterable_item_added:
271275
self._do_item_added(iterable_item_added, insert=True)
272276

277+
if iterable_item_moved:
278+
added_dict = {v["new_path"]: v["value"] for k, v in iterable_item_moved.items()}
279+
self._do_item_added(added_dict, insert=False)
280+
273281
def _do_dictionary_item_added(self):
274282
dictionary_item_added = self.diff.get('dictionary_item_added')
275283
if dictionary_item_added:

deepdiff/diff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type
709709
x,
710710
y,
711711
child_relationship_class=child_relationship_class,
712-
child_relationship_param=i)
712+
child_relationship_param=j)
713713
self._diff(next_level, parents_ids_added)
714714

715715
def _diff_str(self, level):

tests/fixtures/compare_func_result1.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
"root['Cars'][3]['production']"
77
],
88
"values_changed": {
9-
"root['Cars'][0]['dealers'][1]['quantity']": {
9+
"root['Cars'][2]['dealers'][0]['quantity']": {
1010
"new_value": 50,
1111
"old_value": 20
1212
},
13-
"root['Cars'][2]['model_numbers'][2]": {
13+
"root['Cars'][1]['model_numbers'][2]": {
1414
"new_value": 3,
1515
"old_value": 4
1616
},
@@ -20,20 +20,20 @@
2020
}
2121
},
2222
"iterable_item_added": {
23-
"root['Cars'][0]['dealers'][1]": {
23+
"root['Cars'][2]['dealers'][1]": {
2424
"id": 200,
2525
"address": "200 Fake St",
2626
"quantity": 10
2727
},
28-
"root['Cars'][2]['model_numbers'][3]": 4,
28+
"root['Cars'][1]['model_numbers'][3]": 4,
2929
"root['Cars'][0]": {
3030
"id": "7",
3131
"make": "Toyota",
3232
"model": "8Runner"
3333
}
3434
},
3535
"iterable_item_removed": {
36-
"root['Cars'][0]['dealers'][0]": {
36+
"root['Cars'][2]['dealers'][0]": {
3737
"id": 103,
3838
"address": "103 Fake St",
3939
"quantity": 50

tests/test_delta.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,7 +1478,7 @@ def test_compare_func_with_duplicates_removed(self):
14781478
t2 = [{'id': 3, 'val': 3}, {'id': 2, 'val': 2}, {'id': 1, 'val': 3}]
14791479
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
14801480
expected = {
1481-
'values_changed': {"root[0]['val']": {'new_value': 3, 'old_value': 1}},
1481+
'values_changed': {"root[2]['val']": {'new_value': 3, 'old_value': 1}},
14821482
'iterable_item_removed': {'root[2]': {'id': 1, 'val': 3}},
14831483
'iterable_item_moved': {
14841484
'root[0]': {'new_path': 'root[2]', 'value': {'id': 1, 'val': 3}},
@@ -1495,7 +1495,7 @@ def test_compare_func_with_duplicates_added(self):
14951495
t2 = [{'id': 1, 'val': 1}, {'id': 2, 'val': 2}, {'id': 1, 'val': 3}, {'id': 3, 'val': 3}]
14961496
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
14971497
expected = {
1498-
'values_changed': {"root[2]['val']": {'new_value': 1, 'old_value': 3}},
1498+
'values_changed': {"root[0]['val']": {'new_value': 1, 'old_value': 3}},
14991499
'iterable_item_added': {'root[2]': {'id': 1, 'val': 3}},
15001500
'iterable_item_moved': {
15011501
'root[2]': {'new_path': 'root[0]', 'value': {'id': 1, 'val': 1}},
@@ -1526,3 +1526,72 @@ def test_compare_func_path_specific(self):
15261526
delta = Delta(ddiff)
15271527
recreated_t2 = t1 + delta
15281528
assert t2 == recreated_t2
1529+
1530+
def test_compare_func_nested_changes(self):
1531+
t1 = {
1532+
"TestTable": [
1533+
{
1534+
"id": "022fb580-800e-11ea-a361-39b3dada34b5",
1535+
"name": "Max",
1536+
"NestedTable": [
1537+
{
1538+
"id": "022fb580-800e-11ea-a361-39b3dada34a6",
1539+
"NestedField": "Test Field"
1540+
}
1541+
]
1542+
},
1543+
{
1544+
"id": "022fb580-800e-11ea-a361-12354656532",
1545+
"name": "Bob",
1546+
"NestedTable": [
1547+
{
1548+
"id": "022fb580-800e-11ea-a361-39b3dada34c7",
1549+
"NestedField": "Test Field 2"
1550+
},
1551+
]
1552+
},
1553+
]
1554+
}
1555+
t2 = {"TestTable": [
1556+
{
1557+
"id": "022fb580-800e-11ea-a361-12354656532",
1558+
"name": "Bob (Changed Name)",
1559+
"NestedTable": [
1560+
{
1561+
"id": "022fb580-800e-11ea-a361-39b3dada34c7",
1562+
"NestedField": "Test Field 2 (Changed Nested Field)"
1563+
},
1564+
{
1565+
"id": "new id",
1566+
"NestedField": "Test Field 3"
1567+
},
1568+
{
1569+
"id": "newer id",
1570+
"NestedField": "Test Field 4"
1571+
},
1572+
]
1573+
},
1574+
{
1575+
"id": "adding_some_random_id",
1576+
"name": "New Name",
1577+
"NestedTable": [
1578+
{
1579+
"id": "random_nested_id_added",
1580+
"NestedField": "New Nested Field"
1581+
},
1582+
{
1583+
"id": "random_nested_id_added2",
1584+
"NestedField": "New Nested Field2"
1585+
},
1586+
{
1587+
"id": "random_nested_id_added3",
1588+
"NestedField": "New Nested Field43"
1589+
},
1590+
]
1591+
}
1592+
]}
1593+
1594+
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func, verbose_level=2)
1595+
delta = Delta(ddiff)
1596+
recreated_t2 = t1 + delta
1597+
assert t2 == recreated_t2

0 commit comments

Comments
 (0)