Simplify diff producer: no $replace, no value escaping
The producer now emits only plain key assignment (full-value replacement for scalars, lists and type changes, including dict-over-non-dict) and $delete for removed keys. $-prefixed keys are still escaped as $$; values are stored verbatim. The consumer remains compatible with jsondiff-produced diffs ($replace, positional $insert/$delete, per-index list diffs). A dict diff against a list state is treated as jsondiff list-ops only when every key is a command or integer position, otherwise as our wholesale replacement. Unescaping is narrowed to the $$ prefix so verbatim single-$ values pass through untouched.
This commit is contained in:
+37
-9
@@ -63,9 +63,9 @@ def test_list_with_unchanged_prefix_is_full_assignment():
|
||||
|
||||
def test_type_changes_are_full_assignment():
|
||||
assert compute_diff({"a": {"x": 1}}, {"a": [1]}) == {"a": [1]}
|
||||
# A dict replacing a non-dict needs $replace (a bare dict would read as
|
||||
# a nested diff); this matches jsondiff.
|
||||
assert compute_diff({"a": [1]}, {"a": {"x": 1}}) == {"a": {"$replace": {"x": 1}}}
|
||||
# A dict replacing a non-dict is a plain assignment too: the consumer
|
||||
# sees from the old value whether to patch (dict) or replace.
|
||||
assert compute_diff({"a": [1]}, {"a": {"x": 1}}) == {"a": {"x": 1}}
|
||||
assert compute_diff({"a": 1}, {"a": None}) == {"a": None}
|
||||
|
||||
|
||||
@@ -79,12 +79,12 @@ def test_dollar_keys_escaped():
|
||||
assert compute_diff({"$weird": 1}, {}) == {"$delete": ["$$weird"]}
|
||||
|
||||
|
||||
def test_dollar_values_escaped():
|
||||
assert compute_diff({"s": 1}, {"s": "$y"}) == {"s": "$$y"}
|
||||
assert compute_diff({"s": 1}, {"s": "$delete"}) == {"s": "$$delete"}
|
||||
# Nested values in wholesale assignments are escaped too.
|
||||
def test_dollar_values_not_escaped():
|
||||
# Only keys are escaped; values are stored verbatim, even "$delete".
|
||||
assert compute_diff({"s": 1}, {"s": "$y"}) == {"s": "$y"}
|
||||
assert compute_diff({"s": 1}, {"s": "$delete"}) == {"s": "$delete"}
|
||||
assert compute_diff({}, {"o": {"s": "$y", "l": ["$z"]}}) == {
|
||||
"o": {"s": "$$y", "l": ["$$z"]}
|
||||
"o": {"s": "$y", "l": ["$z"]}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,8 +144,12 @@ def test_apply_list_per_index_nested_diff():
|
||||
def test_apply_escaped_keys_and_values():
|
||||
assert apply_diff({}, {"$$weird": 1}) == {"$weird": 1}
|
||||
assert apply_diff({"$weird": 1}, {"$delete": ["$$weird"]}) == {}
|
||||
# jsondiff escapes $-values as "$$.."; those are unescaped on apply.
|
||||
assert apply_diff({"s": 1}, {"s": "$$y"}) == {"s": "$y"}
|
||||
assert apply_diff({"s": 1}, {"s": "$$delete"}) == {"s": "$delete"}
|
||||
# Our own producer stores values verbatim; single-$ stays as-is.
|
||||
assert apply_diff({"s": 1}, {"s": "$y"}) == {"s": "$y"}
|
||||
assert apply_diff({"s": 1}, {"s": "$delete"}) == {"s": "$delete"}
|
||||
assert apply_diff({}, {"o": {"s": "$$y", "l": ["$$z"]}}) == {
|
||||
"o": {"s": "$y", "l": ["$z"]}
|
||||
}
|
||||
@@ -159,6 +163,19 @@ def test_apply_diff_on_missing_state():
|
||||
assert apply_diff({}, {"a": {"b": 1}}) == {"a": {"b": 1}}
|
||||
|
||||
|
||||
def test_apply_bare_dict_replaces_non_dict():
|
||||
# Our own producer emits no $replace; a dict over a non-dict old value
|
||||
# is a wholesale replacement.
|
||||
assert apply_diff({"a": [1, 2]}, {"a": {"x": 1}}) == {"a": {"x": 1}}
|
||||
assert apply_diff({"a": 5}, {"a": {"x": 1}}) == {"a": {"x": 1}}
|
||||
assert apply_diff({"a": None}, {"a": {"x": 1}}) == {"a": {"x": 1}}
|
||||
|
||||
|
||||
def test_apply_list_patch_still_works_on_lists():
|
||||
# jsondiff-style per-index diff keeps list-op semantics on list state.
|
||||
assert apply_diff({"l": [1, 2]}, {"l": {"1": 9}}) == {"l": [1, 9]}
|
||||
|
||||
|
||||
# --- jsondiff compatibility, both directions --------------------------------
|
||||
|
||||
COMPAT_CASES = [
|
||||
@@ -188,7 +205,18 @@ COMPAT_CASES = [
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name,old,new", COMPAT_CASES, ids=[c[0] for c in COMPAT_CASES])
|
||||
# jsondiff.patch cannot apply our patches for "type change list->dict"
|
||||
# (we emit a bare dict where jsondiff needs $replace) and "dollar value"
|
||||
# (we do not escape "$"-prefixed values; jsondiff.patch would strip the
|
||||
# "$"), so those cases are excluded from this direction.
|
||||
JSONDIFF_APPLIES_CASES = [
|
||||
c for c in COMPAT_CASES if c[0] not in {"type change list->dict", "dollar value"}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"name,old,new", JSONDIFF_APPLIES_CASES, ids=[c[0] for c in JSONDIFF_APPLIES_CASES]
|
||||
)
|
||||
def test_jsondiff_applies_our_patches(name, old, new):
|
||||
diff = compute_diff(old, new)
|
||||
assert diff is not None
|
||||
|
||||
Reference in New Issue
Block a user