From f7b67be42b2517d2e47345b6e71c1795b35f93ac Mon Sep 17 00:00:00 2001 From: youdie006 Date: Thu, 3 Sep 2026 15:48:51 +0900 Subject: [PATCH] Strip the whole escape_str when unescaping _escape prepends options.escape_str and _symbol_map is keyed on it, but _unescape stripped exactly one character. With a multi-character escape_str, unmarshal did not invert marshal and patch produced both a corrupted key and the stale original. --- jsondiff/__init__.py | 2 +- tests/test_jsondiff.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/jsondiff/__init__.py b/jsondiff/__init__.py index 981ba7b..948b1af 100644 --- a/jsondiff/__init__.py +++ b/jsondiff/__init__.py @@ -1031,7 +1031,7 @@ def _unescape(self, x): if sym is not None: return sym if x.startswith(self.options.escape_str): - return x[1:] + return x[len(self.options.escape_str):] return x def unmarshal(self, d): diff --git a/tests/test_jsondiff.py b/tests/test_jsondiff.py index fd015cf..4cfdf34 100644 --- a/tests/test_jsondiff.py +++ b/tests/test_jsondiff.py @@ -95,6 +95,28 @@ def test_marshal(self): self.assertEqual(d, differ.unmarshal(dm)) + def test_marshal_multi_char_escape_str(self): + differ = JsonDiffer(escape_str='__') + + d = { + delete: 3, + '__delete': 4, + insert: 4, + '____something': 1 + } + + dm = differ.marshal(d) + + self.assertEqual(d, differ.unmarshal(dm)) + + def test_patch_multi_char_escape_str(self): + a = {'__x': 1} + b = {'__x': 2} + + d = diff(a, b, marshal=True, escape_str='__') + + self.assertEqual(b, jsondiff.patch(a, d, marshal=True, escape_str='__')) + @given(strategies.randoms().map(generate_scenario_no_sets)) @settings(max_examples=1000) def test_dump(self, scenario):