@@ -2,16 +2,14 @@ JSON Collection Strategies
22==========================
33
44``DeepJSONDiff `` is an opt-in facade for comparing JSON-like values whose nested
5- arrays require path-specific semantics. It canonicalizes both inputs without
6- mutating them and delegates the final recursive comparison to ``DeepDiff ``.
7- Existing ``DeepDiff `` behaviour is unchanged.
5+ arrays require path-specific semantics. It builds canonical caller-isolated
6+ views and delegates the final recursive comparison to ``DeepDiff ``. Existing
7+ ``DeepDiff `` behaviour is unchanged.
88
99Basic identity matching
1010-----------------------
1111
12- Use ``match_by `` when a list contains records with a stable business identity.
13- Reordering does not create differences, while additions, removals and field
14- changes remain visible::
12+ Use ``match_by `` when list records have a stable business identity::
1513
1614 from deepdiff import CollectionStrategy, DeepJSONDiff
1715
@@ -26,144 +24,137 @@ changes remain visible::
2624 ],
2725 )
2826
29- Nested arrays and composite keys
30- --------------------------------
27+ Reordering does not create differences. Additions, removals, and field changes
28+ remain visible against canonical identity-keyed paths.
3129
32- Array wildcards select exactly one array index under every matching parent.
33- Object-key wildcards likewise select exactly one key. Relative identity fields
34- may be nested and composite::
35-
36- CollectionStrategy(
37- path="$.orders[*].items",
38- match_by=("product.id", "warehouse.code"),
39- )
30+ Selectors
31+ ---------
4032
41- CollectionStrategy(
42- path="$.*.users",
43- match_by=("id",),
44- )
33+ Selectors use a deliberately small JSONPath-like grammar:
4534
46- Identity fields must resolve to JSON scalar values: ``None ``, booleans,
47- integers, finite floats, or strings. Structured values such as dictionaries and
48- lists are rejected because they do not provide a stable business identity.
35+ - ``$.users `` selects an object key.
36+ - ``$.groups[0].users `` selects one array index.
37+ - ``$.groups[*].users `` matches exactly one array index.
38+ - ``$.*.users `` matches exactly one object key.
39+ - ``$['a-b'] `` selects a quoted key that cannot be written safely in dot form.
4940
50- Filtering
51- ---------
41+ Wildcards never cross additional levels. Diagnostic paths use the same quoted
42+ key syntax and can be reused as selectors.
5243
53- ``filter_func `` is applied symmetrically to defensive copies of both inputs
54- before matching and comparison. A filter may therefore inspect or mutate the
55- value it receives without changing caller-owned payloads. Filter counts are
56- available through ``get_stats() ``::
44+ Relative identity and sort fields use dotted extraction and may include numeric
45+ list indexes, such as ``product.id `` or ``versions.0.number ``.
5746
58- strategy = CollectionStrategy(
59- path="$.users",
60- match_by=("id",),
61- filter_func=lambda item: item.get("active") is True,
62- )
47+ Identity values
48+ ---------------
6349
64- Sorting and order-insensitive arrays
65- ------------------------------------
50+ Identity fields must resolve, after normalization, to finite JSON scalar values:
51+ ``None ``, booleans, integers, finite floats, or strings. Structured and
52+ non-finite values are rejected.
6653
67- `` sort_by `` creates deterministic ordering for arrays where identity matching is
68- not required. Numeric values use numeric ordering; missing, null, mixed , and
69- structured values use a stable type-aware order .
54+ Composite identities are encoded using a canonical type-preserving format, so
55+ values such as integer `` 1 ``, float `` 1.0 `` , and string `` "1" `` remain distinct
56+ and delimiter characters cannot cause collisions .
7057
71- `` compare_as_set `` performs multiset (bag) comparison for scalar arrays. Order
72- is ignored but duplicate counts remain significant::
58+ Filtering, normalization, and exclusion
59+ ---------------------------------------
7360
74- CollectionStrategy(path="$.events", sort_by=("timestamp", "id"))
75- CollectionStrategy(path="$.roles", compare_as_set=True)
61+ ``filter_func `` and normalizers receive defensive copies and may mutate them
62+ without modifying caller-owned inputs. Copies are created only when callbacks
63+ are configured; otherwise canonicalization rebuilds the structure directly.
7664
77- `` compare_as_set `` cannot be combined with `` match_by `` or `` sort_by ``.
65+ Processing order is:
7866
79- Missing identities
80- ------------------
67+ 1. Copy when callbacks require isolation.
68+ 2. Apply ``filter_func ``.
69+ 3. Apply normalizers.
70+ 4. Extract identity fields.
71+ 5. Remove ``exclude_fields ``.
72+ 6. Canonicalize nested content.
8173
82- The default ``MissingIdentityPolicy.FALLBACK `` retains records lacking one or
83- more identity fields and compares them using their relative order. Strict and
84- exclusion policies are also available. Enum members and their string values are
85- accepted::
74+ Identity fields may therefore also appear in ``exclude_fields ``. They are used
75+ for matching but need not remain in the compared record.
8676
87- from deepdiff import MissingIdentityPolicy
77+ Sorting
78+ -------
8879
89- CollectionStrategy(
90- path="$.items",
91- match_by=("id",),
92- missing_identity=MissingIdentityPolicy.ERROR,
93- )
80+ ``sort_by `` creates a total, type-stable order for JSON-compatible values.
81+ Integers and floats are intentionally distinguished. Finite values, infinities,
82+ and NaN values have deterministic positions and do not produce mixed-type sort
83+ errors.
9484
95- CollectionStrategy(
96- path="$.items",
97- match_by=("id",),
98- missing_identity="exclude",
99- )
85+ Structured sort keys are supported only for JSON lists and mappings with string
86+ keys. Tuples, sets, mappings with non-string keys, and arbitrary objects are
87+ rejected rather than ordered through unstable ``repr `` output.
10088
101- Duplicate identities
102- --------------------
89+ Order-insensitive scalar arrays
90+ -------------------------------
10391
104- Duplicate identities raise ``DuplicateIdentityError `` by default. To compare a
105- duplicate group, use ``DuplicateIdentityPolicy.GROUP `` and preferably provide a
106- secondary ``sort_by `` key::
92+ ``compare_as_set=True `` performs multiset (bag) comparison:
10793
108- from deepdiff import DuplicateIdentityPolicy
94+ - order is ignored;
95+ - duplicate counts remain significant;
96+ - integer and float values remain type-distinct;
97+ - only finite JSON scalar values are accepted.
10998
110- CollectionStrategy(
111- path="$.items",
112- match_by=("id",),
113- sort_by=("version",),
114- duplicates=DuplicateIdentityPolicy.GROUP,
115- )
99+ It cannot be combined with ``match_by `` or ``sort_by ``.
116100
117- Duplicate identities are encoded with a canonical, type-preserving format, so
118- distinct composite identities cannot collide.
101+ Missing identities
102+ ------------------
119103
120- Normalization and volatile fields
121- ---------------------------------
104+ The default ``MissingIdentityPolicy.FALLBACK `` retains records missing one or
105+ more identity fields and compares them in relative order. ``EXCLUDE `` omits
106+ them, and ``ERROR `` raises ``IdentityExtractionError ``. Enum members and their
107+ string values are accepted.
122108
123- Normalizers run before identity extraction and comparison. Each selected item
124- is deep-copied before filters and normalizers run, so callbacks may mutate their
125- argument without mutating the caller's input. ``exclude_fields `` removes
126- volatile top-level fields from each selected array item::
109+ Duplicate identities
110+ --------------------
127111
128- CollectionStrategy(
129- path="$.events",
130- match_by=("eventId",),
131- normalizers=(normalize_region,),
132- exclude_fields=("requestId", "generatedAt"),
133- )
112+ Duplicate identities raise ``DuplicateIdentityError `` by default.
113+ ``DuplicateIdentityPolicy.GROUP `` retains every record under the identity.
114+ Provide ``sort_by `` when duplicate-group order is not meaningful.
134115
135116Rule precedence
136117---------------
137118
138- When multiple rules match a concrete path, higher ``priority `` wins. At equal
139- priority, the pattern with more exact tokens wins. Equally specific matches are
140- rejected as ambiguous instead of being resolved silently .
119+ Higher ``priority `` wins when multiple strategies match . At equal priority, the
120+ pattern with more exact tokens wins. Equally specific matches are rejected as
121+ ambiguous.
141122
142123Diagnostics
143124-----------
144125
145- ``get_stats() `` returns per-concrete-path execution information, including
146- input counts, filtered counts, missing identities, duplicate groups, and the
147- selected strategy name.
126+ ``get_stats() `` separates diagnostics by input side::
148127
149- Result interface
150- ----------------
128+ stats = diff.get_stats()
129+ left_users = stats["left"]["$.users"]
130+ right_users = stats["right"]["$.users"]
151131
152- ``DeepJSONDiff `` implements the standard read-only mapping interface and
153- delegates serialization to the underlying ``DeepDiff `` result::
132+ Each entry includes the selected strategy, input item count, filtered count,
133+ missing-identity count, and duplicate-group count. Keeping sides separate
134+ prevents statistics for different logical parents from being merged when a
135+ parent identity-matched collection is reordered.
154136
155- if diff:
156- print(diff["values_changed"])
137+ DeepDiff keyword compatibility
138+ ------------------------------
157139
158- for category, changes in diff.items():
159- print(category, changes)
140+ Identity matching changes selected arrays into canonical mappings. DeepDiff
141+ options that interpret caller-visible paths or iterable positions would
142+ therefore operate on a different structure. ``DeepJSONDiff `` rejects these
143+ path-sensitive options instead of silently changing their meaning:
160144
161- print(diff.to_json(sort_keys=True))
145+ - ``include_paths ``
146+ - ``exclude_paths ``
147+ - ``exclude_regex_paths ``
148+ - ``ignore_order_func ``
149+ - ``iterable_compare_func ``
150+ - ``custom_operators ``
162151
163- Compatibility
164- -------------
152+ Other keyword arguments are forwarded to the underlying ``DeepDiff `` instance.
153+
154+ Result interface
155+ ----------------
165156
166- ``DeepJSONDiff `` is separate from ``DeepDiff `` by design. Existing constructor
167- parameters, result views, Delta behaviour, iterable callbacks, and
168- multiprocessing paths are not modified. Keyword arguments unrelated to
169- collection strategies are forwarded to the underlying `` DeepDiff `` instance .
157+ ``DeepJSONDiff `` is a composition-based facade, not a complete ``DeepDiff ``
158+ subclass. It implements the read-only mapping interface and exposes `` diff `` for
159+ direct access to the underlying result. `` to_dict() `` and `` to_json() `` are
160+ delegated explicitly .
0 commit comments