Skip to content

Handle self-containing values in make_patch and add #180 regression tests - #200

Merged
stefankoegl merged 4 commits into
masterfrom
claude/festive-keller-4l9i1i
Oct 9, 2026
Merged

stefankoegl merged 4 commits into
masterfrom
claude/festive-keller-4l9i1i

Conversation

@stefankoegl

@stefankoegl stefankoegl commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

#180 has two halves, and both are now fixed on master. #181 stopped comparing list items with Python equality. #206 stopped matching moved values that way by adding DiffBuilder._move_key. This PR started as its own fix for the second half. After merging master, it keeps master's implementation and adds a fix for values that contain themselves, plus regression tests for #180.

Changes

  • Values that contain themselves no longer crash make_patch. _move_key and _differing_runs serialize values with _sorted_members, which recurses until RecursionError on a value that contains itself. On master, adding or removing such a value raises that error, which did not happen before Improve list diffing to avoid unnecessary item replacements #201 and Improve list diffing with sequence alignment to avoid cascading changes #206. Both now treat RecursionError like any other value that dumps cannot serialize. Such values are moved only where the same value is added unchanged, and lists containing them are compared by position.
  • Regression tests for Bug: jsondiff accepts list items as equal when their __eq__ returns True #180 in tests.py:
    • test_move_only_values_equal_in_json gets more cases:
      • {"x": 1} and {"x": true}
      • 1 and 1.0
      • 0.0 and -0.0
      • member names that are not strings, such as {1: 'v'} and {True: 'v'}
    • test_move_only_values_equal_in_given_dumps: a custom dumps decides which values are the same, e.g. Decimal('1.0') and Decimal('1.00').
    • test_use_move_regardless_of_member_order: a value is still moved when its object members are in a different order.
    • test_values_that_contain_themselves: adding, removing and moving a value that contains itself.
  • Pinned property-test examples in property_tests.py: random search finds the bug only rarely. So the counterexamples for both halves of Bug: jsondiff accepts list items as equal when their __eq__ returns True #180, [0] -> [False] and {'a': [1]} -> {'b': [True]}, are pinned as examples on test_roundtrip_of_safe_documents.

Testing

  • These all pass: python tests.py, python property_tests.py (8 expected failures, as on master), python ext_tests.py and pytest.
  • CI passes on Python 3.10 to 3.14.
  • With master's unmodified jsonpatch.py, only test_values_that_contain_themselves fails, with RecursionError.

Closes #180

🤖 Generated with Claude Code

https://claude.ai/code/session_013AREgGKdBTdMJKwCsPuhNn

make_patch found moved values by looking up (value, type(value)) in a
dict, or with == for unhashable values. Python considers e.g. [1] and
[True], {'x': 1} and {'x': True}, or 0.0 and -0.0 equal, so a removed
value could be moved to where a different one was added, and the patch
did not produce the target document.

Move detection now keys values the way _compare_values compares them:
by their serialization with the diff's dumps, with objects and arrays
taken member by member so that the order of object members still does
not matter. Values that dumps cannot serialize are added and removed
instead of moved, so make_patch still accepts them. The key is computed
once per added or removed value, and as all keys are hashable the
linear search for unhashable values is gone.

The {'a': [1]} -> {'b': [True]} example of test_roundtrip passes now and
is dropped; the test still fails for the object member '-'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AREgGKdBTdMJKwCsPuhNn

This comment was marked as resolved.

The safe documents left out booleans only because make_patch confused
them with numbers (#180). Now that it does not, they are generated
again, and the counterexamples of both halves of #180 are pinned on
test_roundtrip_of_safe_documents, as random search finds them rarely.

The test for values that cannot be serialized now checks that they are
removed and added, as applying a move would give the same document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AREgGKdBTdMJKwCsPuhNn

This comment was marked as resolved.

The keys that find moved values used member names as they are, so
{1: 'v'} and {True: 'v'} shared a key although they serialize as
{"1": "v"} and {"true": "v"}. Member names that are not strings are
serialized now.

Values that contain themselves made building their key recurse until
RecursionError, which crashed make_patch. Like other values that cannot
be serialized, they are now added and removed instead of moved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AREgGKdBTdMJKwCsPuhNn

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Serialization-key collisions can still generate moves that produce incorrect destination values.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread jsonpatch.py Outdated
Comment thread jsonpatch.py Outdated
master fixed the move detection of #180 on its own, with _move_key,
which serializes values with their object members sorted. Its version
is kept, replacing index_key and _serialized_key of this branch, and so
is its behaviour for values that dumps cannot serialize: they are moved
where they are added unchanged, instead of being removed and added.

_move_key and _differing_runs now also treat RecursionError as a value
that cannot be serialized. On master, adding or removing a value that
contains itself crashed make_patch, which it did not before.

The cases of test_issue180_moves are merged into master's
test_move_only_values_equal_in_json. test_values_that_cannot_be_serialized
is dropped, as master's test_values_dumps_cannot_serialize covers such
values with the behaviour master chose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013AREgGKdBTdMJKwCsPuhNn
@stefankoegl stefankoegl changed the title Fix move detection to respect JSON serialization equality Handle self-containing values in make_patch and add #180 regression tests Oct 9, 2026
@stefankoegl
stefankoegl merged commit e0e7703 into master Oct 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: jsondiff accepts list items as equal when their __eq__ returns True

3 participants