Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions jsonpatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,9 @@ def _move_key(self, value):
try:
# Serialized like in _differing_runs
return self.dumps(_sorted_members(value))
except (TypeError, ValueError):
# Values that cannot be serialized are only moved where they are
# added unchanged
except (TypeError, ValueError, RecursionError):
# Values that cannot be serialized, e.g. as they contain
# themselves, are only moved where they are added unchanged
return id(value)

def store_index(self, value, index, st):
Expand Down Expand Up @@ -838,7 +838,7 @@ def _differing_runs(self, src, dst):
# but independent of the order of object members
src_keys = [self.dumps(_sorted_members(item)) for item in src]
dst_keys = [self.dumps(_sorted_members(item)) for item in dst]
except (TypeError, ValueError):
except (TypeError, ValueError, RecursionError):
# Items that cannot be serialized are compared by position
return [(0, len(src), 0, len(dst))]

Expand Down
3 changes: 3 additions & 0 deletions property_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,9 @@ def test_roundtrip(self, docs):
self.check_roundtrip(*docs)

@given(safe_doc_pairs)
# the diff considered e.g. 1 and true equal, #180
@example(docs=([0], [False]))
@example(docs=({'a': [1]}, {'b': [True]}))
def test_roundtrip_of_safe_documents(self, docs):
self.check_roundtrip(*docs)

Expand Down
33 changes: 33 additions & 0 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,13 +647,28 @@ def test_move_only_values_equal_in_json(self):
([[1], [2], [3]], [[2], [3], [True]]),
([{'a': 1}, 2], [2, {'a': True}]),
({'a': [1]}, {'b': [True]}),
({'a': {'x': 1}}, {'b': {'x': True}}),
([[1], 0], [0, [True]]),
({'a': [1]}, {'b': [1.0]}),
({'a': 0.0}, {'b': -0.0}),
# member names that are not strings are serialized, too
({'a': {1: 'v'}}, {'b': {True: 'v'}}),
]
for src, dst in cases:
with self.subTest(src=src, dst=dst):
patch = jsonpatch.make_patch(src, dst)
res = patch.apply(src)
self.assertEqual(json.dumps(res), json.dumps(dst))

def test_move_only_values_equal_in_given_dumps(self):
"""The given dumps decides which values are the same"""
src = {'a': decimal.Decimal('1.0')}
dst = {'b': decimal.Decimal('1.00')}
patch = jsonpatch.JsonPatch.from_diff(
src, dst, dumps=custom_types_dumps)
res = jsonpatch.apply_patch(src, patch)
self.assertEqual(str(res['b']), '1.00')

def test_values_dumps_cannot_serialize(self):
"""Such values can be added, removed and moved, as they need not be
compared"""
Expand All @@ -671,6 +686,18 @@ def test_values_dumps_cannot_serialize(self):
self.assertEqual(jsonpatch.make_patch(*cases[-1]).patch,
[{'op': 'move', 'from': '/a', 'path': '/b'}])

def test_values_that_contain_themselves(self):
"""dumps cannot serialize them either, but they can still be added,
removed and moved"""
value = []
value.append(value)
self.assertEqual(jsonpatch.make_patch({}, {'a': value}).patch,
[{'op': 'add', 'path': '/a', 'value': value}])
self.assertEqual(jsonpatch.make_patch({'a': [value]}, {'a': []}).patch,
[{'op': 'remove', 'path': '/a/0'}])
self.assertEqual(jsonpatch.make_patch({'a': value}, {'b': value}).patch,
[{'op': 'move', 'from': '/a', 'path': '/b'}])

def test_issue119(self):
"""Make sure it avoids casting numeric str dict key to int"""
src = [
Expand Down Expand Up @@ -900,6 +927,12 @@ def fn(_src, _dst):
fn({'foo': [1, 2, 3]}, {'foo': [3, 2, 1]})
fn([1, 2, 3], [3, 2, 1])

def test_use_move_regardless_of_member_order(self):
src = {'a': {'x': 1, 'y': [True, {}]}}
dst = {'b': {'y': [True, {}], 'x': 1}}
patch = list(jsonpatch.make_patch(src, dst))
self.assertEqual(patch, [{'op': 'move', 'from': '/a', 'path': '/b'}])

def test_success_if_replace_inside_dict(self):
src = [{'a': 1, 'foo': {'b': 2, 'd': 5}}]
dst = [{'a': 1, 'foo': {'b': 3, 'd': 6}}]
Expand Down
Loading