@@ -38,20 +38,10 @@ def _normalise_json(obj):
3838def _json_subset_matches (expected , actual ) -> bool :
3939 """Return *True* when *expected* is a subset of *actual* (recursive).
4040
41- **List matching is greedy and order-sensitive.** Each item in *expected*
42- is matched against *actual* in order, claiming the first unused actual
43- item that satisfies the subset check. This means an earlier expected
44- item can consume the only actual item that a later, more specific
45- expected item would need. For example, with::
46-
47- expected = [{"a": 1}, {"a": 1, "b": 2}]
48- actual = [{"a": 1, "b": 2}]
49-
50- the first expected item matches ``{"a": 1, "b": 2}`` (leaving nothing
51- for the second), so the overall match returns *False* even though
52- ``{"a": 1, "b": 2}`` satisfies the second item. Consumers should
53- **not** rely on non-deterministic matching; instead, pre-order *expected*
54- lists from most-specific to least-specific to avoid this behaviour.
41+ List matching uses backtracking so that all possible assignments between
42+ expected and actual items are explored. This avoids the greedy pitfall
43+ where an earlier expected item claims the only actual item that a later,
44+ more specific expected item would need.
5545 """
5646 if isinstance (expected , dict ):
5747 if not isinstance (actual , dict ):
@@ -63,17 +53,18 @@ def _json_subset_matches(expected, actual) -> bool:
6353 if isinstance (expected , list ):
6454 if not isinstance (actual , list ):
6555 return False
66- matched = [False ] * len (actual )
67- for exp_item in expected :
68- found = False
56+
57+ def _try_match (exp_index : int , used : set ) -> bool :
58+ if exp_index == len (expected ):
59+ return True
60+ exp_item = expected [exp_index ]
6961 for i , act_item in enumerate (actual ):
70- if not matched [i ] and _json_subset_matches (exp_item , act_item ):
71- matched [i ] = True
72- found = True
73- break
74- if not found :
75- return False
76- return True
62+ if i not in used and _json_subset_matches (exp_item , act_item ):
63+ if _try_match (exp_index + 1 , used | {i }):
64+ return True
65+ return False
66+
67+ return _try_match (0 , set ())
7768 return expected == actual
7869
7970
0 commit comments