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
5 changes: 4 additions & 1 deletion jsonpatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,9 @@ def from_diff(
one. The resulting patch transforms `src` into `dst`, so it has to be
applied to `src` (or to a document equal to it).

Values in the patch are copied, so changing either input document
afterwards does not change the generated patch.

:param src: Source JSON document, including arrays and scalar values.
:type src: object

Expand Down Expand Up @@ -906,7 +909,7 @@ def from_diff(
optimization=optimization,
generate_test_ops=generate_test_ops)
builder._compare_values((), None, src, dst)
ops = list(builder.execute())
ops = copy.deepcopy(list(builder.execute()))
return cls(ops, pointer_cls=pointer_cls)

def to_string(self, dumps=None):
Expand Down
39 changes: 37 additions & 2 deletions tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,36 @@ def json_loader(obj):

class MakePatchTestCase(unittest.TestCase):

def test_generated_values_do_not_alias_target(self):
documents = [
({}, {'a': {'nested': [1]}}),
({'a': 0}, {'a': {'nested': [1]}}),
([], [{'nested': [1]}]),
([0], [{'nested': [1]}]),
(None, {'a': {'nested': [1]}}),
]
for src, dst in documents:
for optimization in (True, False):
with self.subTest(src=src, optimization=optimization):
target = copy.deepcopy(dst)
patch = jsonpatch.JsonPatch.from_diff(
src, target, optimization=optimization)
serialized = patch.to_string()
value = target[0] if isinstance(target, list) else target['a']
value['nested'].append(2)

self.assertEqual(patch.to_string(), serialized)
self.assertEqual(patch.apply(src), dst)

def test_editing_generated_values_does_not_change_target(self):
for src in ({}, {'a': 0}):
with self.subTest(src=src):
dst = {'a': {'nested': [1]}}
patch = jsonpatch.make_patch(src, dst)
patch.patch[0]['value']['nested'].append(2)
self.assertEqual(dst, {'a': {'nested': [1]}})
self.assertEqual(patch.apply(src), {'a': {'nested': [1, 2]}})

def test_apply_patch_to_copy(self):
src = {'foo': 'bar', 'boo': 'qux'}
dst = {'baz': 'qux', 'foo': 'boo'}
Expand Down Expand Up @@ -874,8 +904,13 @@ def test_values_that_contain_themselves(self):
removed and moved"""
value = []
value.append(value)
self.assertEqual(jsonpatch.make_patch({}, {'a': value}).patch,
[{'op': 'add', 'path': '/a', 'value': value}])
patch = jsonpatch.make_patch({}, {'a': value}).patch
self.assertEqual(len(patch), 1)
self.assertEqual(patch[0]['op'], 'add')
self.assertEqual(patch[0]['path'], '/a')
copied = patch[0]['value']
self.assertIsNot(copied, value)
self.assertIs(copied[0], copied)
self.assertEqual(jsonpatch.make_patch({'a': [value]}, {'a': []}).patch,
[{'op': 'remove', 'path': '/a/0'}])
self.assertEqual(jsonpatch.make_patch({'a': value}, {'b': value}).patch,
Expand Down
Loading