jsonl-diff 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonl-diff
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A lightweight, pure-Python tool for comparing large JSONL/NDJSON datasets without loading them entirely into memory.
5
5
  Author-email: rmoralespp <rmoralespp@gmail.com>
6
6
  License: MIT License
@@ -49,8 +49,11 @@ Classifier: Topic :: File Formats :: JSON
49
49
  Requires-Python: >=3.8
50
50
  Description-Content-Type: text/markdown
51
51
  License-File: LICENSE
52
- Requires-Dist: py-jsonl==1.5.2
52
+ Requires-Dist: ijson>=3.2
53
+ Requires-Dist: py-jsonl==1.5.3
53
54
  Requires-Dist: jmespath>=1.0.0
55
+ Provides-Extra: speedups
56
+ Requires-Dist: msgspec>=0.18; python_version >= "3.10" and extra == "speedups"
54
57
  Dynamic: license-file
55
58
 
56
59
  # jsonl-diff
@@ -63,12 +66,12 @@ Dynamic: license-file
63
66
  <img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
64
67
  </p>
65
68
 
66
- Compare large JSONL/NDJSON datasets **by record identity instead of line
67
- position**, without loading the complete inputs into memory.
69
+ A lightweight, pure-Python tool for comparing large JSONL/NDJSON datasets and
70
+ top-level JSON arrays **by record identity instead of line position**, without
71
+ loading the complete inputs into memory.
68
72
 
69
73
  `jsonl-diff` matches records using one or more top-level fields and reports:
70
- `equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each
71
- source.
74
+ `equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each source.
72
75
 
73
76
  It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
74
77
 
@@ -80,6 +83,19 @@ It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
80
83
  pip install jsonl-diff
81
84
  ```
82
85
 
86
+ ### Optional speedups
87
+
88
+ For large datasets, install the optional [`msgspec`](https://jcristharif.com/msgspec/)
89
+ accelerator (Python 3.10+), which uses a C parser and encoder for JSON decoding
90
+ and record canonicalization, speeding up large diffs by roughly **2x**:
91
+
92
+ ```bash
93
+ pip install "jsonl-diff[speedups]"
94
+ ```
95
+
96
+ It is used automatically when available — no configuration or code changes. When
97
+ `msgspec` is absent, the pure-Python path runs instead and produces identical results.
98
+
83
99
  ## Quick start
84
100
 
85
101
  Given:
@@ -119,23 +135,27 @@ Record order does not matter.
119
135
 
120
136
  * Single or composite top-level identities.
121
137
  * Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
122
- * Optional **JMESPath** filtering with `--where`.
138
+ * Optional **[JMESPath](https://jmespath.org/)** filtering with `--where`.
123
139
  * Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
124
- * Exact **RFC 6901** JSON Pointer ignores.
140
+ * Exact **[RFC 6901](https://www.rfc-editor.org/info/rfc6901/)** JSON Pointer ignores.
125
141
  * Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
126
142
  * Semantic number comparison using `Decimal`.
143
+ * Optional `msgspec` accelerator for ~2x faster large diffs (`pip install "jsonl-diff[speedups]"`).
127
144
  * Deterministic summaries and change iteration.
128
145
  * Original OLD/NEW physical line numbers.
129
146
  * Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
130
147
  * Local, HTTP/HTTPS, file-like, and supported compressed sources.
148
+ * Incremental top-level JSON array parsing with `--format json`.
131
149
  * CLI and Python API using the same comparison engine.
132
150
 
133
151
  ## CLI
134
152
 
135
153
  ```text
136
154
  jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
137
- [--duplicates {error,first,last}] [--details FILE] [--quiet]
155
+ [--duplicates {error,first,last}] [--missing-key {error,null}]
156
+ [--details FILE] [--quiet]
138
157
  [--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
158
+ [--format {jsonl,json}]
139
159
  old new
140
160
  ```
141
161
 
@@ -146,11 +166,13 @@ jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
146
166
  | `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
147
167
  | `--where EXPRESSION` | JMESPath filter applied to each record |
148
168
  | `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
169
+ | `--missing-key POLICY`| `error` (default), or `null` to treat an absent identity field as `null` |
149
170
  | `--details FILE` | Write deterministic machine-readable JSONL changes |
150
171
  | `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
151
172
  | `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
152
173
  | `--quiet` | Suppress the normal summary |
153
174
  | `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
175
+ | `--format FORMAT` | Input format: `jsonl` (default) or a top-level JSON array with `json` |
154
176
 
155
177
  Examples:
156
178
 
@@ -169,6 +191,11 @@ jsonl-diff old.jsonl new.jsonl \
169
191
  --key id \
170
192
  --where 'deleted_at == `null`'
171
193
 
194
+ # Tolerate an identity field that is absent from some records
195
+ jsonl-diff old.jsonl new.jsonl \
196
+ --key country,customerId \
197
+ --missing-key null
198
+
172
199
  # Detect observed schema drift
173
200
  jsonl-diff old.jsonl new.jsonl \
174
201
  --key id \
@@ -177,6 +204,9 @@ jsonl-diff old.jsonl new.jsonl \
177
204
 
178
205
  # Compressed input
179
206
  jsonl-diff old.jsonl.xz new.jsonl.gz --key id
207
+
208
+ # Top-level JSON arrays (parsed incrementally)
209
+ jsonl-diff old.json new.json --key id --format json
180
210
  ```
181
211
 
182
212
  ### Exit codes
@@ -237,8 +267,12 @@ signature, result models, `Decimal` key semantics, and error hierarchy.
237
267
 
238
268
  `jsonl-diff` is intentionally strict:
239
269
 
240
- * Every input line must contain exactly one valid top-level JSON object.
241
- * Blank lines, malformed JSON, duplicate properties, `NaN`, and infinities are rejected.
270
+ * In the default `jsonl` format, every input line must contain exactly one
271
+ valid top-level JSON object.
272
+ * With `--format json`, each input must be one top-level JSON array whose
273
+ elements are the records.
274
+ * Blank lines, malformed JSON, `NaN`, and infinities are rejected.
275
+ * Duplicate object property names within a record follow JSON's last-wins semantics: the last occurrence is kept.
242
276
  * Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
243
277
  * Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
244
278
  * Duplicate identities fail by default; `--duplicates first`/`last` select one
@@ -259,7 +293,9 @@ order, and canonical number formatting.
259
293
  ## Sources & compression
260
294
 
261
295
  Supported sources include local paths and HTTP/HTTPS URLs. The Python API
262
- also accepts file-like objects.
296
+ also accepts file-like objects. JSON arrays use `py-jsonl.open_stream()` for
297
+ the source and `ijson` for incremental parsing, so compression and remote
298
+ source handling are shared with JSONL input.
263
299
 
264
300
  Supported compression:
265
301
 
@@ -8,12 +8,12 @@
8
8
  <img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
9
9
  </p>
10
10
 
11
- Compare large JSONL/NDJSON datasets **by record identity instead of line
12
- position**, without loading the complete inputs into memory.
11
+ A lightweight, pure-Python tool for comparing large JSONL/NDJSON datasets and
12
+ top-level JSON arrays **by record identity instead of line position**, without
13
+ loading the complete inputs into memory.
13
14
 
14
15
  `jsonl-diff` matches records using one or more top-level fields and reports:
15
- `equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each
16
- source.
16
+ `equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each source.
17
17
 
18
18
  It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
19
19
 
@@ -25,6 +25,19 @@ It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
25
25
  pip install jsonl-diff
26
26
  ```
27
27
 
28
+ ### Optional speedups
29
+
30
+ For large datasets, install the optional [`msgspec`](https://jcristharif.com/msgspec/)
31
+ accelerator (Python 3.10+), which uses a C parser and encoder for JSON decoding
32
+ and record canonicalization, speeding up large diffs by roughly **2x**:
33
+
34
+ ```bash
35
+ pip install "jsonl-diff[speedups]"
36
+ ```
37
+
38
+ It is used automatically when available — no configuration or code changes. When
39
+ `msgspec` is absent, the pure-Python path runs instead and produces identical results.
40
+
28
41
  ## Quick start
29
42
 
30
43
  Given:
@@ -64,23 +77,27 @@ Record order does not matter.
64
77
 
65
78
  * Single or composite top-level identities.
66
79
  * Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
67
- * Optional **JMESPath** filtering with `--where`.
80
+ * Optional **[JMESPath](https://jmespath.org/)** filtering with `--where`.
68
81
  * Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
69
- * Exact **RFC 6901** JSON Pointer ignores.
82
+ * Exact **[RFC 6901](https://www.rfc-editor.org/info/rfc6901/)** JSON Pointer ignores.
70
83
  * Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
71
84
  * Semantic number comparison using `Decimal`.
85
+ * Optional `msgspec` accelerator for ~2x faster large diffs (`pip install "jsonl-diff[speedups]"`).
72
86
  * Deterministic summaries and change iteration.
73
87
  * Original OLD/NEW physical line numbers.
74
88
  * Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
75
89
  * Local, HTTP/HTTPS, file-like, and supported compressed sources.
90
+ * Incremental top-level JSON array parsing with `--format json`.
76
91
  * CLI and Python API using the same comparison engine.
77
92
 
78
93
  ## CLI
79
94
 
80
95
  ```text
81
96
  jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
82
- [--duplicates {error,first,last}] [--details FILE] [--quiet]
97
+ [--duplicates {error,first,last}] [--missing-key {error,null}]
98
+ [--details FILE] [--quiet]
83
99
  [--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
100
+ [--format {jsonl,json}]
84
101
  old new
85
102
  ```
86
103
 
@@ -91,11 +108,13 @@ jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
91
108
  | `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
92
109
  | `--where EXPRESSION` | JMESPath filter applied to each record |
93
110
  | `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
111
+ | `--missing-key POLICY`| `error` (default), or `null` to treat an absent identity field as `null` |
94
112
  | `--details FILE` | Write deterministic machine-readable JSONL changes |
95
113
  | `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
96
114
  | `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
97
115
  | `--quiet` | Suppress the normal summary |
98
116
  | `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
117
+ | `--format FORMAT` | Input format: `jsonl` (default) or a top-level JSON array with `json` |
99
118
 
100
119
  Examples:
101
120
 
@@ -114,6 +133,11 @@ jsonl-diff old.jsonl new.jsonl \
114
133
  --key id \
115
134
  --where 'deleted_at == `null`'
116
135
 
136
+ # Tolerate an identity field that is absent from some records
137
+ jsonl-diff old.jsonl new.jsonl \
138
+ --key country,customerId \
139
+ --missing-key null
140
+
117
141
  # Detect observed schema drift
118
142
  jsonl-diff old.jsonl new.jsonl \
119
143
  --key id \
@@ -122,6 +146,9 @@ jsonl-diff old.jsonl new.jsonl \
122
146
 
123
147
  # Compressed input
124
148
  jsonl-diff old.jsonl.xz new.jsonl.gz --key id
149
+
150
+ # Top-level JSON arrays (parsed incrementally)
151
+ jsonl-diff old.json new.json --key id --format json
125
152
  ```
126
153
 
127
154
  ### Exit codes
@@ -182,8 +209,12 @@ signature, result models, `Decimal` key semantics, and error hierarchy.
182
209
 
183
210
  `jsonl-diff` is intentionally strict:
184
211
 
185
- * Every input line must contain exactly one valid top-level JSON object.
186
- * Blank lines, malformed JSON, duplicate properties, `NaN`, and infinities are rejected.
212
+ * In the default `jsonl` format, every input line must contain exactly one
213
+ valid top-level JSON object.
214
+ * With `--format json`, each input must be one top-level JSON array whose
215
+ elements are the records.
216
+ * Blank lines, malformed JSON, `NaN`, and infinities are rejected.
217
+ * Duplicate object property names within a record follow JSON's last-wins semantics: the last occurrence is kept.
187
218
  * Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
188
219
  * Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
189
220
  * Duplicate identities fail by default; `--duplicates first`/`last` select one
@@ -204,7 +235,9 @@ order, and canonical number formatting.
204
235
  ## Sources & compression
205
236
 
206
237
  Supported sources include local paths and HTTP/HTTPS URLs. The Python API
207
- also accepts file-like objects.
238
+ also accepts file-like objects. JSON arrays use `py-jsonl.open_stream()` for
239
+ the source and `ijson` for incremental parsing, so compression and remote
240
+ source handling are shared with JSONL input.
208
241
 
209
242
  Supported compression:
210
243
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jsonl-diff
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A lightweight, pure-Python tool for comparing large JSONL/NDJSON datasets without loading them entirely into memory.
5
5
  Author-email: rmoralespp <rmoralespp@gmail.com>
6
6
  License: MIT License
@@ -49,8 +49,11 @@ Classifier: Topic :: File Formats :: JSON
49
49
  Requires-Python: >=3.8
50
50
  Description-Content-Type: text/markdown
51
51
  License-File: LICENSE
52
- Requires-Dist: py-jsonl==1.5.2
52
+ Requires-Dist: ijson>=3.2
53
+ Requires-Dist: py-jsonl==1.5.3
53
54
  Requires-Dist: jmespath>=1.0.0
55
+ Provides-Extra: speedups
56
+ Requires-Dist: msgspec>=0.18; python_version >= "3.10" and extra == "speedups"
54
57
  Dynamic: license-file
55
58
 
56
59
  # jsonl-diff
@@ -63,12 +66,12 @@ Dynamic: license-file
63
66
  <img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
64
67
  </p>
65
68
 
66
- Compare large JSONL/NDJSON datasets **by record identity instead of line
67
- position**, without loading the complete inputs into memory.
69
+ A lightweight, pure-Python tool for comparing large JSONL/NDJSON datasets and
70
+ top-level JSON arrays **by record identity instead of line position**, without
71
+ loading the complete inputs into memory.
68
72
 
69
73
  `jsonl-diff` matches records using one or more top-level fields and reports:
70
- `equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each
71
- source.
74
+ `equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each source.
72
75
 
73
76
  It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
74
77
 
@@ -80,6 +83,19 @@ It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
80
83
  pip install jsonl-diff
81
84
  ```
82
85
 
86
+ ### Optional speedups
87
+
88
+ For large datasets, install the optional [`msgspec`](https://jcristharif.com/msgspec/)
89
+ accelerator (Python 3.10+), which uses a C parser and encoder for JSON decoding
90
+ and record canonicalization, speeding up large diffs by roughly **2x**:
91
+
92
+ ```bash
93
+ pip install "jsonl-diff[speedups]"
94
+ ```
95
+
96
+ It is used automatically when available — no configuration or code changes. When
97
+ `msgspec` is absent, the pure-Python path runs instead and produces identical results.
98
+
83
99
  ## Quick start
84
100
 
85
101
  Given:
@@ -119,23 +135,27 @@ Record order does not matter.
119
135
 
120
136
  * Single or composite top-level identities.
121
137
  * Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
122
- * Optional **JMESPath** filtering with `--where`.
138
+ * Optional **[JMESPath](https://jmespath.org/)** filtering with `--where`.
123
139
  * Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
124
- * Exact **RFC 6901** JSON Pointer ignores.
140
+ * Exact **[RFC 6901](https://www.rfc-editor.org/info/rfc6901/)** JSON Pointer ignores.
125
141
  * Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
126
142
  * Semantic number comparison using `Decimal`.
143
+ * Optional `msgspec` accelerator for ~2x faster large diffs (`pip install "jsonl-diff[speedups]"`).
127
144
  * Deterministic summaries and change iteration.
128
145
  * Original OLD/NEW physical line numbers.
129
146
  * Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
130
147
  * Local, HTTP/HTTPS, file-like, and supported compressed sources.
148
+ * Incremental top-level JSON array parsing with `--format json`.
131
149
  * CLI and Python API using the same comparison engine.
132
150
 
133
151
  ## CLI
134
152
 
135
153
  ```text
136
154
  jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
137
- [--duplicates {error,first,last}] [--details FILE] [--quiet]
155
+ [--duplicates {error,first,last}] [--missing-key {error,null}]
156
+ [--details FILE] [--quiet]
138
157
  [--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
158
+ [--format {jsonl,json}]
139
159
  old new
140
160
  ```
141
161
 
@@ -146,11 +166,13 @@ jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
146
166
  | `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
147
167
  | `--where EXPRESSION` | JMESPath filter applied to each record |
148
168
  | `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
169
+ | `--missing-key POLICY`| `error` (default), or `null` to treat an absent identity field as `null` |
149
170
  | `--details FILE` | Write deterministic machine-readable JSONL changes |
150
171
  | `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
151
172
  | `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
152
173
  | `--quiet` | Suppress the normal summary |
153
174
  | `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
175
+ | `--format FORMAT` | Input format: `jsonl` (default) or a top-level JSON array with `json` |
154
176
 
155
177
  Examples:
156
178
 
@@ -169,6 +191,11 @@ jsonl-diff old.jsonl new.jsonl \
169
191
  --key id \
170
192
  --where 'deleted_at == `null`'
171
193
 
194
+ # Tolerate an identity field that is absent from some records
195
+ jsonl-diff old.jsonl new.jsonl \
196
+ --key country,customerId \
197
+ --missing-key null
198
+
172
199
  # Detect observed schema drift
173
200
  jsonl-diff old.jsonl new.jsonl \
174
201
  --key id \
@@ -177,6 +204,9 @@ jsonl-diff old.jsonl new.jsonl \
177
204
 
178
205
  # Compressed input
179
206
  jsonl-diff old.jsonl.xz new.jsonl.gz --key id
207
+
208
+ # Top-level JSON arrays (parsed incrementally)
209
+ jsonl-diff old.json new.json --key id --format json
180
210
  ```
181
211
 
182
212
  ### Exit codes
@@ -237,8 +267,12 @@ signature, result models, `Decimal` key semantics, and error hierarchy.
237
267
 
238
268
  `jsonl-diff` is intentionally strict:
239
269
 
240
- * Every input line must contain exactly one valid top-level JSON object.
241
- * Blank lines, malformed JSON, duplicate properties, `NaN`, and infinities are rejected.
270
+ * In the default `jsonl` format, every input line must contain exactly one
271
+ valid top-level JSON object.
272
+ * With `--format json`, each input must be one top-level JSON array whose
273
+ elements are the records.
274
+ * Blank lines, malformed JSON, `NaN`, and infinities are rejected.
275
+ * Duplicate object property names within a record follow JSON's last-wins semantics: the last occurrence is kept.
242
276
  * Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
243
277
  * Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
244
278
  * Duplicate identities fail by default; `--duplicates first`/`last` select one
@@ -259,7 +293,9 @@ order, and canonical number formatting.
259
293
  ## Sources & compression
260
294
 
261
295
  Supported sources include local paths and HTTP/HTTPS URLs. The Python API
262
- also accepts file-like objects.
296
+ also accepts file-like objects. JSON arrays use `py-jsonl.open_stream()` for
297
+ the source and `ijson` for incremental parsing, so compression and remote
298
+ source handling are shared with JSONL input.
263
299
 
264
300
  Supported compression:
265
301
 
@@ -0,0 +1,8 @@
1
+ ijson>=3.2
2
+ py-jsonl==1.5.3
3
+ jmespath>=1.0.0
4
+
5
+ [speedups]
6
+
7
+ [speedups:python_version >= "3.10"]
8
+ msgspec>=0.18