From 2294792469c9c5294c6e2385a95326b971a88b6b Mon Sep 17 00:00:00 2001 From: Apeksha_2022 <70309060+Apeksha9389@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:19:43 +0530 Subject: [PATCH] fix: honor optimization=False when generating JSON patches --- jsonpatch.py | 15 +++++++++++++-- property_tests.py | 9 +++++++++ tests.py | 19 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/jsonpatch.py b/jsonpatch.py index ee7e301..9b7b9e1 100644 --- a/jsonpatch.py +++ b/jsonpatch.py @@ -597,6 +597,10 @@ def from_diff( :param dst: Data target document object. :type dst: dict + :param optimization: Generate move operations for matching values. + Set to False to use only add, remove and replace. + :type optimization: bool + :param dumps: A function of one argument that produces a serialized JSON string. :type dumps: Callable @@ -614,7 +618,8 @@ def from_diff( True """ json_dumper = dumps or cls.json_dumper - builder = DiffBuilder(src, dst, json_dumper, pointer_cls=pointer_cls) + builder = DiffBuilder(src, dst, json_dumper, pointer_cls=pointer_cls, + optimization=optimization) builder._compare_values((), None, src, dst) ops = list(builder.execute()) return cls(ops, pointer_cls=pointer_cls) @@ -670,9 +675,11 @@ def _get_operation(self, operation): class DiffBuilder(object): - def __init__(self, src_doc, dst_doc, dumps=json.dumps, pointer_cls=JsonPointer): + def __init__(self, src_doc, dst_doc, dumps=json.dumps, pointer_cls=JsonPointer, + optimization=True): self.dumps = dumps self.pointer_cls = pointer_cls + self.optimization = optimization self.index_storage = [{}, {}] self.__root = root = [] self.src_doc = src_doc @@ -693,10 +700,14 @@ def _move_key(self, value): return id(value) def store_index(self, value, index, st): + if not self.optimization: + return storage = self.index_storage[st] storage.setdefault(self._move_key(value), []).append(index) def take_index(self, value, st): + if not self.optimization: + return None stored = self.index_storage[st].get(self._move_key(value)) if stored: return stored.pop() diff --git a/property_tests.py b/property_tests.py index 545abd7..3d41ab5 100644 --- a/property_tests.py +++ b/property_tests.py @@ -290,6 +290,15 @@ def test_diff_of_equal_documents_is_empty(self, doc): def test_roundtrip(self, docs): self.check_roundtrip(*docs) + @given(doc_pairs) + def test_roundtrip_without_move_optimization(self, docs): + src, dst = docs + patch = jsonpatch.JsonPatch.from_diff(src, dst, optimization=False) + note(patch.patch) + self.assertTrue(all(op['op'] in ('add', 'remove', 'replace') + for op in patch)) + assert_json_equal(patch.apply(src), dst) + @given(safe_doc_pairs) # the diff considered e.g. 1 and true equal, #180 @example(docs=([0], [False])) diff --git a/tests.py b/tests.py index 57ad381..24a0ab3 100755 --- a/tests.py +++ b/tests.py @@ -21,6 +21,25 @@ class ApplyPatchTestCase(unittest.TestCase): + def test_diff_without_optimization_uses_basic_operations(self): + documents = [ + ({'old': {'value': 1}}, {'new': {'value': 1}}), + ([1, 2, 3], [3, 1, 2]), + ([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]), + ({'a/b': [1, 2]}, {'~key': [1, 2]}), + ] + for src, dst in documents: + with self.subTest(src=src, dst=dst): + original = copy.deepcopy(src) + patch = jsonpatch.JsonPatch.from_diff( + src, dst, optimization=False) + self.assertTrue(all(operation['op'] in + ('add', 'remove', 'replace') + for operation in patch)) + self.assertEqual(patch.apply(src), dst) + self.assertEqual(src, original) + + def test_js_file(self): with open('./tests.js', 'r') as f: tests = json.load(f)