jsonl-diff 0.1.1__tar.gz → 0.1.3__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.
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/PKG-INFO +63 -34
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/README.md +60 -33
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/jsonl_diff.egg-info/PKG-INFO +63 -34
- jsonl_diff-0.1.3/jsonl_diff.egg-info/requires.txt +7 -0
- jsonl_diff-0.1.3/jsonl_diff.py +1512 -0
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/pyproject.toml +7 -1
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/tests/test_cli.py +307 -1
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/tests/test_diff.py +391 -6
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/tests/test_sources.py +31 -0
- jsonl_diff-0.1.1/jsonl_diff.egg-info/requires.txt +0 -2
- jsonl_diff-0.1.1/jsonl_diff.py +0 -758
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/LICENSE +0 -0
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/jsonl_diff.egg-info/SOURCES.txt +0 -0
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/jsonl_diff.egg-info/dependency_links.txt +0 -0
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/jsonl_diff.egg-info/entry_points.txt +0 -0
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/jsonl_diff.egg-info/top_level.txt +0 -0
- {jsonl_diff-0.1.1 → jsonl_diff-0.1.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonl-diff
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -51,6 +51,8 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
License-File: LICENSE
|
|
52
52
|
Requires-Dist: py-jsonl==1.5.2
|
|
53
53
|
Requires-Dist: jmespath>=1.0.0
|
|
54
|
+
Provides-Extra: speedups
|
|
55
|
+
Requires-Dist: msgspec>=0.18; python_version >= "3.10" and extra == "speedups"
|
|
54
56
|
Dynamic: license-file
|
|
55
57
|
|
|
56
58
|
# jsonl-diff
|
|
@@ -63,15 +65,11 @@ Dynamic: license-file
|
|
|
63
65
|
<img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
|
|
64
66
|
</p>
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
A lightweight, pure-Python tool for comparing large [JSONL](https://jsonlines.org/)/[NDJSON](https://github.com/ndjson/ndjson-spec) datasets **by record identity instead of line
|
|
67
69
|
position**, without loading the complete inputs into memory.
|
|
68
70
|
|
|
69
71
|
`jsonl-diff` matches records using one or more top-level fields and reports:
|
|
70
|
-
|
|
71
|
-
* `equal`
|
|
72
|
-
* `added`
|
|
73
|
-
* `deleted`
|
|
74
|
-
* `modified`
|
|
72
|
+
`equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each source.
|
|
75
73
|
|
|
76
74
|
It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
77
75
|
|
|
@@ -79,12 +77,23 @@ It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
|
79
77
|
|
|
80
78
|
## Installation
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
```bash
|
|
81
|
+
pip install jsonl-diff
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Optional speedups
|
|
85
|
+
|
|
86
|
+
For large datasets, install the optional [`msgspec`](https://jcristharif.com/msgspec/)
|
|
87
|
+
accelerator (Python 3.10+), which uses a C parser and encoder for JSON decoding
|
|
88
|
+
and record canonicalization, speeding up large diffs by roughly **2x**:
|
|
83
89
|
|
|
84
90
|
```bash
|
|
85
|
-
|
|
91
|
+
pip install "jsonl-diff[speedups]"
|
|
86
92
|
```
|
|
87
93
|
|
|
94
|
+
It is used automatically when available — no configuration or code changes. When
|
|
95
|
+
`msgspec` is absent, the pure-Python path runs instead and produces identical results.
|
|
96
|
+
|
|
88
97
|
## Quick start
|
|
89
98
|
|
|
90
99
|
Given:
|
|
@@ -114,6 +123,8 @@ Records:
|
|
|
114
123
|
added: 1
|
|
115
124
|
deleted: 0
|
|
116
125
|
modified: 1
|
|
126
|
+
OLD duplicates: 0
|
|
127
|
+
NEW duplicates: 0
|
|
117
128
|
```
|
|
118
129
|
|
|
119
130
|
Record order does not matter.
|
|
@@ -121,14 +132,16 @@ Record order does not matter.
|
|
|
121
132
|
## Features
|
|
122
133
|
|
|
123
134
|
* Single or composite top-level identities.
|
|
124
|
-
* Disk-backed comparison for large datasets ([architecture](docs/architecture.md)).
|
|
125
|
-
* Optional JMESPath filtering with `--where`.
|
|
126
|
-
* Configurable duplicate handling: `error`, `first`, or `last`.
|
|
127
|
-
* Exact RFC 6901 JSON Pointer ignores.
|
|
135
|
+
* Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
|
|
136
|
+
* Optional **[JMESPath](https://jmespath.org/)** filtering with `--where`.
|
|
137
|
+
* Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
|
|
138
|
+
* Exact **[RFC 6901](https://www.rfc-editor.org/info/rfc6901/)** JSON Pointer ignores.
|
|
139
|
+
* Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
|
|
128
140
|
* Semantic number comparison using `Decimal`.
|
|
141
|
+
* Optional `msgspec` accelerator for ~2x faster large diffs (`pip install "jsonl-diff[speedups]"`).
|
|
129
142
|
* Deterministic summaries and change iteration.
|
|
130
143
|
* Original OLD/NEW physical line numbers.
|
|
131
|
-
* Machine-readable JSONL change log with `--details` ([format](docs/details-format.md)).
|
|
144
|
+
* Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
|
|
132
145
|
* Local, HTTP/HTTPS, file-like, and supported compressed sources.
|
|
133
146
|
* CLI and Python API using the same comparison engine.
|
|
134
147
|
|
|
@@ -137,7 +150,7 @@ Record order does not matter.
|
|
|
137
150
|
```text
|
|
138
151
|
jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
139
152
|
[--duplicates {error,first,last}] [--details FILE] [--quiet]
|
|
140
|
-
[--max-temp MAX_TEMP]
|
|
153
|
+
[--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
|
|
141
154
|
old new
|
|
142
155
|
```
|
|
143
156
|
|
|
@@ -147,10 +160,12 @@ jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
|
147
160
|
| `--key KEY` | Required top-level identity field; repeat or comma-separate for composite keys |
|
|
148
161
|
| `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
|
|
149
162
|
| `--where EXPRESSION` | JMESPath filter applied to each record |
|
|
150
|
-
| `--duplicates POLICY` | `error` (default),
|
|
163
|
+
| `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
|
|
151
164
|
| `--details FILE` | Write deterministic machine-readable JSONL changes |
|
|
165
|
+
| `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
|
|
166
|
+
| `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
|
|
152
167
|
| `--quiet` | Suppress the normal summary |
|
|
153
|
-
| `--max-temp BYTES` |
|
|
168
|
+
| `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
|
|
154
169
|
|
|
155
170
|
Examples:
|
|
156
171
|
|
|
@@ -169,18 +184,24 @@ jsonl-diff old.jsonl new.jsonl \
|
|
|
169
184
|
--key id \
|
|
170
185
|
--where 'deleted_at == `null`'
|
|
171
186
|
|
|
187
|
+
# Detect observed schema drift
|
|
188
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
189
|
+
--key id \
|
|
190
|
+
--schema-diff \
|
|
191
|
+
--schema-ignore /metadata
|
|
192
|
+
|
|
172
193
|
# Compressed input
|
|
173
194
|
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
|
|
174
195
|
```
|
|
175
196
|
|
|
176
197
|
### Exit codes
|
|
177
198
|
|
|
178
|
-
| Code | Meaning
|
|
179
|
-
| ---: |
|
|
180
|
-
| `0` |
|
|
181
|
-
| `1` |
|
|
182
|
-
| `2` | Input, resource, output, or runtime error
|
|
183
|
-
| `3` | Invalid CLI configuration or usage
|
|
199
|
+
| Code | Meaning |
|
|
200
|
+
| ---: | ---------------------------------------------------------- |
|
|
201
|
+
| `0` | No record, duplicate, or requested observed-schema issues |
|
|
202
|
+
| `1` | Record differences, tolerated duplicates, or schema changes |
|
|
203
|
+
| `2` | Input, resource, output, or runtime error |
|
|
204
|
+
| `3` | Invalid CLI configuration or usage |
|
|
184
205
|
|
|
185
206
|
## Python API
|
|
186
207
|
|
|
@@ -193,11 +214,15 @@ with diff(
|
|
|
193
214
|
key=("country", "customerId"),
|
|
194
215
|
ignore=("/updated_at",),
|
|
195
216
|
where='country == `"ES"`',
|
|
217
|
+
schema_diff=True,
|
|
196
218
|
) as result:
|
|
197
219
|
print(result.summary)
|
|
198
220
|
|
|
199
221
|
for change in result.changes(ChangeOperation.MODIFIED):
|
|
200
222
|
print(change.key, change.old_line, change.new_line)
|
|
223
|
+
|
|
224
|
+
for change in result.schema_changes():
|
|
225
|
+
print(change.operation, change.path)
|
|
201
226
|
```
|
|
202
227
|
|
|
203
228
|
`diff()` returns a disk-backed `DiffResult`, used as a context manager.
|
|
@@ -220,7 +245,7 @@ The public exception hierarchy is rooted at `JsonlDiffError`:
|
|
|
220
245
|
* `DuplicateKeyError`
|
|
221
246
|
* `ResourceError`
|
|
222
247
|
|
|
223
|
-
See the [Python API reference](docs/python-api.md) for the full callable
|
|
248
|
+
See the [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) for the full callable
|
|
224
249
|
signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
225
250
|
|
|
226
251
|
## Comparison semantics
|
|
@@ -228,18 +253,22 @@ signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
|
228
253
|
`jsonl-diff` is intentionally strict:
|
|
229
254
|
|
|
230
255
|
* Every input line must contain exactly one valid top-level JSON object.
|
|
231
|
-
* Blank lines, malformed JSON,
|
|
256
|
+
* Blank lines, malformed JSON, `NaN`, and infinities are rejected.
|
|
257
|
+
* Duplicate object property names within a record follow JSON's last-wins semantics: the last occurrence is kept.
|
|
232
258
|
* Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
|
|
233
259
|
* Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
|
|
234
|
-
* Duplicate identities fail by default; `--duplicates first`/`last`
|
|
260
|
+
* Duplicate identities fail by default; `--duplicates first`/`last` select one
|
|
261
|
+
occurrence, report every discarded occurrence, and return exit code `1`.
|
|
235
262
|
* Object property order is ignored; array order is significant.
|
|
236
263
|
* Numbers are compared by mathematical value: `1`, `1.0`, and `1e0` are equal.
|
|
237
264
|
* Unicode strings are compared without normalization.
|
|
238
265
|
* Changes are reported in deterministic identity order.
|
|
239
266
|
|
|
240
267
|
`--where` selects which records participate; `--key` defines identity;
|
|
241
|
-
`--ignore` removes fields from content comparison.
|
|
242
|
-
|
|
268
|
+
`--ignore` removes fields from content comparison. With `--schema-diff`,
|
|
269
|
+
`--schema-ignore` independently removes fields from observed-schema profiling.
|
|
270
|
+
See
|
|
271
|
+
[Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) for the full rules,
|
|
243
272
|
including duplicate handling, ignore-pointer edge cases, `--where` evaluation
|
|
244
273
|
order, and canonical number formatting.
|
|
245
274
|
|
|
@@ -255,7 +284,7 @@ Supported compression:
|
|
|
255
284
|
* xz
|
|
256
285
|
* Zstandard on Python 3.14
|
|
257
286
|
|
|
258
|
-
See [architecture](docs/architecture.md) for the full source/compression
|
|
287
|
+
See [architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) for the full source/compression
|
|
259
288
|
support matrix and how sources are delegated to `py-jsonl`.
|
|
260
289
|
|
|
261
290
|
## Limitations
|
|
@@ -268,7 +297,7 @@ support matrix and how sources are delegated to `py-jsonl`.
|
|
|
268
297
|
* nested identities or automatic key detection
|
|
269
298
|
* fuzzy matching or numeric tolerances
|
|
270
299
|
* unordered-array comparison
|
|
271
|
-
* schema
|
|
300
|
+
* validation against a declared schema or input repair
|
|
272
301
|
* ZIP, database, or cloud-provider inputs
|
|
273
302
|
* GUI or HTML reports
|
|
274
303
|
|
|
@@ -276,10 +305,10 @@ It is a **dataset reconciliation tool**, not a general-purpose visual JSON diff.
|
|
|
276
305
|
|
|
277
306
|
## Further reading
|
|
278
307
|
|
|
279
|
-
* [Comparison semantics](docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
280
|
-
* [Disk-backed architecture](docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
281
|
-
* [Details JSONL format](docs/details-format.md) — machine-readable `--details` output schema.
|
|
282
|
-
* [Python API reference](docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
308
|
+
* [Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
309
|
+
* [Disk-backed architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
310
|
+
* [Details JSONL format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md) — machine-readable `--details` output schema.
|
|
311
|
+
* [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
283
312
|
|
|
284
313
|
## Development
|
|
285
314
|
|
|
@@ -8,15 +8,11 @@
|
|
|
8
8
|
<img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
A lightweight, pure-Python tool for comparing large [JSONL](https://jsonlines.org/)/[NDJSON](https://github.com/ndjson/ndjson-spec) datasets **by record identity instead of line
|
|
12
12
|
position**, without loading the complete inputs into memory.
|
|
13
13
|
|
|
14
14
|
`jsonl-diff` matches records using one or more top-level fields and reports:
|
|
15
|
-
|
|
16
|
-
* `equal`
|
|
17
|
-
* `added`
|
|
18
|
-
* `deleted`
|
|
19
|
-
* `modified`
|
|
15
|
+
`equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each source.
|
|
20
16
|
|
|
21
17
|
It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
22
18
|
|
|
@@ -24,12 +20,23 @@ It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
|
24
20
|
|
|
25
21
|
## Installation
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
```bash
|
|
24
|
+
pip install jsonl-diff
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Optional speedups
|
|
28
|
+
|
|
29
|
+
For large datasets, install the optional [`msgspec`](https://jcristharif.com/msgspec/)
|
|
30
|
+
accelerator (Python 3.10+), which uses a C parser and encoder for JSON decoding
|
|
31
|
+
and record canonicalization, speeding up large diffs by roughly **2x**:
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
|
-
|
|
34
|
+
pip install "jsonl-diff[speedups]"
|
|
31
35
|
```
|
|
32
36
|
|
|
37
|
+
It is used automatically when available — no configuration or code changes. When
|
|
38
|
+
`msgspec` is absent, the pure-Python path runs instead and produces identical results.
|
|
39
|
+
|
|
33
40
|
## Quick start
|
|
34
41
|
|
|
35
42
|
Given:
|
|
@@ -59,6 +66,8 @@ Records:
|
|
|
59
66
|
added: 1
|
|
60
67
|
deleted: 0
|
|
61
68
|
modified: 1
|
|
69
|
+
OLD duplicates: 0
|
|
70
|
+
NEW duplicates: 0
|
|
62
71
|
```
|
|
63
72
|
|
|
64
73
|
Record order does not matter.
|
|
@@ -66,14 +75,16 @@ Record order does not matter.
|
|
|
66
75
|
## Features
|
|
67
76
|
|
|
68
77
|
* Single or composite top-level identities.
|
|
69
|
-
* Disk-backed comparison for large datasets ([architecture](docs/architecture.md)).
|
|
70
|
-
* Optional JMESPath filtering with `--where`.
|
|
71
|
-
* Configurable duplicate handling: `error`, `first`, or `last`.
|
|
72
|
-
* Exact RFC 6901 JSON Pointer ignores.
|
|
78
|
+
* Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
|
|
79
|
+
* Optional **[JMESPath](https://jmespath.org/)** filtering with `--where`.
|
|
80
|
+
* Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
|
|
81
|
+
* Exact **[RFC 6901](https://www.rfc-editor.org/info/rfc6901/)** JSON Pointer ignores.
|
|
82
|
+
* Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
|
|
73
83
|
* Semantic number comparison using `Decimal`.
|
|
84
|
+
* Optional `msgspec` accelerator for ~2x faster large diffs (`pip install "jsonl-diff[speedups]"`).
|
|
74
85
|
* Deterministic summaries and change iteration.
|
|
75
86
|
* Original OLD/NEW physical line numbers.
|
|
76
|
-
* Machine-readable JSONL change log with `--details` ([format](docs/details-format.md)).
|
|
87
|
+
* Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
|
|
77
88
|
* Local, HTTP/HTTPS, file-like, and supported compressed sources.
|
|
78
89
|
* CLI and Python API using the same comparison engine.
|
|
79
90
|
|
|
@@ -82,7 +93,7 @@ Record order does not matter.
|
|
|
82
93
|
```text
|
|
83
94
|
jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
84
95
|
[--duplicates {error,first,last}] [--details FILE] [--quiet]
|
|
85
|
-
[--max-temp MAX_TEMP]
|
|
96
|
+
[--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
|
|
86
97
|
old new
|
|
87
98
|
```
|
|
88
99
|
|
|
@@ -92,10 +103,12 @@ jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
|
92
103
|
| `--key KEY` | Required top-level identity field; repeat or comma-separate for composite keys |
|
|
93
104
|
| `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
|
|
94
105
|
| `--where EXPRESSION` | JMESPath filter applied to each record |
|
|
95
|
-
| `--duplicates POLICY` | `error` (default),
|
|
106
|
+
| `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
|
|
96
107
|
| `--details FILE` | Write deterministic machine-readable JSONL changes |
|
|
108
|
+
| `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
|
|
109
|
+
| `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
|
|
97
110
|
| `--quiet` | Suppress the normal summary |
|
|
98
|
-
| `--max-temp BYTES` |
|
|
111
|
+
| `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
|
|
99
112
|
|
|
100
113
|
Examples:
|
|
101
114
|
|
|
@@ -114,18 +127,24 @@ jsonl-diff old.jsonl new.jsonl \
|
|
|
114
127
|
--key id \
|
|
115
128
|
--where 'deleted_at == `null`'
|
|
116
129
|
|
|
130
|
+
# Detect observed schema drift
|
|
131
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
132
|
+
--key id \
|
|
133
|
+
--schema-diff \
|
|
134
|
+
--schema-ignore /metadata
|
|
135
|
+
|
|
117
136
|
# Compressed input
|
|
118
137
|
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
|
|
119
138
|
```
|
|
120
139
|
|
|
121
140
|
### Exit codes
|
|
122
141
|
|
|
123
|
-
| Code | Meaning
|
|
124
|
-
| ---: |
|
|
125
|
-
| `0` |
|
|
126
|
-
| `1` |
|
|
127
|
-
| `2` | Input, resource, output, or runtime error
|
|
128
|
-
| `3` | Invalid CLI configuration or usage
|
|
142
|
+
| Code | Meaning |
|
|
143
|
+
| ---: | ---------------------------------------------------------- |
|
|
144
|
+
| `0` | No record, duplicate, or requested observed-schema issues |
|
|
145
|
+
| `1` | Record differences, tolerated duplicates, or schema changes |
|
|
146
|
+
| `2` | Input, resource, output, or runtime error |
|
|
147
|
+
| `3` | Invalid CLI configuration or usage |
|
|
129
148
|
|
|
130
149
|
## Python API
|
|
131
150
|
|
|
@@ -138,11 +157,15 @@ with diff(
|
|
|
138
157
|
key=("country", "customerId"),
|
|
139
158
|
ignore=("/updated_at",),
|
|
140
159
|
where='country == `"ES"`',
|
|
160
|
+
schema_diff=True,
|
|
141
161
|
) as result:
|
|
142
162
|
print(result.summary)
|
|
143
163
|
|
|
144
164
|
for change in result.changes(ChangeOperation.MODIFIED):
|
|
145
165
|
print(change.key, change.old_line, change.new_line)
|
|
166
|
+
|
|
167
|
+
for change in result.schema_changes():
|
|
168
|
+
print(change.operation, change.path)
|
|
146
169
|
```
|
|
147
170
|
|
|
148
171
|
`diff()` returns a disk-backed `DiffResult`, used as a context manager.
|
|
@@ -165,7 +188,7 @@ The public exception hierarchy is rooted at `JsonlDiffError`:
|
|
|
165
188
|
* `DuplicateKeyError`
|
|
166
189
|
* `ResourceError`
|
|
167
190
|
|
|
168
|
-
See the [Python API reference](docs/python-api.md) for the full callable
|
|
191
|
+
See the [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) for the full callable
|
|
169
192
|
signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
170
193
|
|
|
171
194
|
## Comparison semantics
|
|
@@ -173,18 +196,22 @@ signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
|
173
196
|
`jsonl-diff` is intentionally strict:
|
|
174
197
|
|
|
175
198
|
* Every input line must contain exactly one valid top-level JSON object.
|
|
176
|
-
* Blank lines, malformed JSON,
|
|
199
|
+
* Blank lines, malformed JSON, `NaN`, and infinities are rejected.
|
|
200
|
+
* Duplicate object property names within a record follow JSON's last-wins semantics: the last occurrence is kept.
|
|
177
201
|
* Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
|
|
178
202
|
* Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
|
|
179
|
-
* Duplicate identities fail by default; `--duplicates first`/`last`
|
|
203
|
+
* Duplicate identities fail by default; `--duplicates first`/`last` select one
|
|
204
|
+
occurrence, report every discarded occurrence, and return exit code `1`.
|
|
180
205
|
* Object property order is ignored; array order is significant.
|
|
181
206
|
* Numbers are compared by mathematical value: `1`, `1.0`, and `1e0` are equal.
|
|
182
207
|
* Unicode strings are compared without normalization.
|
|
183
208
|
* Changes are reported in deterministic identity order.
|
|
184
209
|
|
|
185
210
|
`--where` selects which records participate; `--key` defines identity;
|
|
186
|
-
`--ignore` removes fields from content comparison.
|
|
187
|
-
|
|
211
|
+
`--ignore` removes fields from content comparison. With `--schema-diff`,
|
|
212
|
+
`--schema-ignore` independently removes fields from observed-schema profiling.
|
|
213
|
+
See
|
|
214
|
+
[Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) for the full rules,
|
|
188
215
|
including duplicate handling, ignore-pointer edge cases, `--where` evaluation
|
|
189
216
|
order, and canonical number formatting.
|
|
190
217
|
|
|
@@ -200,7 +227,7 @@ Supported compression:
|
|
|
200
227
|
* xz
|
|
201
228
|
* Zstandard on Python 3.14
|
|
202
229
|
|
|
203
|
-
See [architecture](docs/architecture.md) for the full source/compression
|
|
230
|
+
See [architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) for the full source/compression
|
|
204
231
|
support matrix and how sources are delegated to `py-jsonl`.
|
|
205
232
|
|
|
206
233
|
## Limitations
|
|
@@ -213,7 +240,7 @@ support matrix and how sources are delegated to `py-jsonl`.
|
|
|
213
240
|
* nested identities or automatic key detection
|
|
214
241
|
* fuzzy matching or numeric tolerances
|
|
215
242
|
* unordered-array comparison
|
|
216
|
-
* schema
|
|
243
|
+
* validation against a declared schema or input repair
|
|
217
244
|
* ZIP, database, or cloud-provider inputs
|
|
218
245
|
* GUI or HTML reports
|
|
219
246
|
|
|
@@ -221,10 +248,10 @@ It is a **dataset reconciliation tool**, not a general-purpose visual JSON diff.
|
|
|
221
248
|
|
|
222
249
|
## Further reading
|
|
223
250
|
|
|
224
|
-
* [Comparison semantics](docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
225
|
-
* [Disk-backed architecture](docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
226
|
-
* [Details JSONL format](docs/details-format.md) — machine-readable `--details` output schema.
|
|
227
|
-
* [Python API reference](docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
251
|
+
* [Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
252
|
+
* [Disk-backed architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
253
|
+
* [Details JSONL format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md) — machine-readable `--details` output schema.
|
|
254
|
+
* [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
228
255
|
|
|
229
256
|
## Development
|
|
230
257
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonl-diff
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -51,6 +51,8 @@ Description-Content-Type: text/markdown
|
|
|
51
51
|
License-File: LICENSE
|
|
52
52
|
Requires-Dist: py-jsonl==1.5.2
|
|
53
53
|
Requires-Dist: jmespath>=1.0.0
|
|
54
|
+
Provides-Extra: speedups
|
|
55
|
+
Requires-Dist: msgspec>=0.18; python_version >= "3.10" and extra == "speedups"
|
|
54
56
|
Dynamic: license-file
|
|
55
57
|
|
|
56
58
|
# jsonl-diff
|
|
@@ -63,15 +65,11 @@ Dynamic: license-file
|
|
|
63
65
|
<img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
|
|
64
66
|
</p>
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
A lightweight, pure-Python tool for comparing large [JSONL](https://jsonlines.org/)/[NDJSON](https://github.com/ndjson/ndjson-spec) datasets **by record identity instead of line
|
|
67
69
|
position**, without loading the complete inputs into memory.
|
|
68
70
|
|
|
69
71
|
`jsonl-diff` matches records using one or more top-level fields and reports:
|
|
70
|
-
|
|
71
|
-
* `equal`
|
|
72
|
-
* `added`
|
|
73
|
-
* `deleted`
|
|
74
|
-
* `modified`
|
|
72
|
+
`equal`, `added`, `deleted`, `modified`, and tolerated duplicates in each source.
|
|
75
73
|
|
|
76
74
|
It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
77
75
|
|
|
@@ -79,12 +77,23 @@ It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
|
79
77
|
|
|
80
78
|
## Installation
|
|
81
79
|
|
|
82
|
-
|
|
80
|
+
```bash
|
|
81
|
+
pip install jsonl-diff
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Optional speedups
|
|
85
|
+
|
|
86
|
+
For large datasets, install the optional [`msgspec`](https://jcristharif.com/msgspec/)
|
|
87
|
+
accelerator (Python 3.10+), which uses a C parser and encoder for JSON decoding
|
|
88
|
+
and record canonicalization, speeding up large diffs by roughly **2x**:
|
|
83
89
|
|
|
84
90
|
```bash
|
|
85
|
-
|
|
91
|
+
pip install "jsonl-diff[speedups]"
|
|
86
92
|
```
|
|
87
93
|
|
|
94
|
+
It is used automatically when available — no configuration or code changes. When
|
|
95
|
+
`msgspec` is absent, the pure-Python path runs instead and produces identical results.
|
|
96
|
+
|
|
88
97
|
## Quick start
|
|
89
98
|
|
|
90
99
|
Given:
|
|
@@ -114,6 +123,8 @@ Records:
|
|
|
114
123
|
added: 1
|
|
115
124
|
deleted: 0
|
|
116
125
|
modified: 1
|
|
126
|
+
OLD duplicates: 0
|
|
127
|
+
NEW duplicates: 0
|
|
117
128
|
```
|
|
118
129
|
|
|
119
130
|
Record order does not matter.
|
|
@@ -121,14 +132,16 @@ Record order does not matter.
|
|
|
121
132
|
## Features
|
|
122
133
|
|
|
123
134
|
* Single or composite top-level identities.
|
|
124
|
-
* Disk-backed comparison for large datasets ([architecture](docs/architecture.md)).
|
|
125
|
-
* Optional JMESPath filtering with `--where`.
|
|
126
|
-
* Configurable duplicate handling: `error`, `first`, or `last`.
|
|
127
|
-
* Exact RFC 6901 JSON Pointer ignores.
|
|
135
|
+
* Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
|
|
136
|
+
* Optional **[JMESPath](https://jmespath.org/)** filtering with `--where`.
|
|
137
|
+
* Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
|
|
138
|
+
* Exact **[RFC 6901](https://www.rfc-editor.org/info/rfc6901/)** JSON Pointer ignores.
|
|
139
|
+
* Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
|
|
128
140
|
* Semantic number comparison using `Decimal`.
|
|
141
|
+
* Optional `msgspec` accelerator for ~2x faster large diffs (`pip install "jsonl-diff[speedups]"`).
|
|
129
142
|
* Deterministic summaries and change iteration.
|
|
130
143
|
* Original OLD/NEW physical line numbers.
|
|
131
|
-
* Machine-readable JSONL change log with `--details` ([format](docs/details-format.md)).
|
|
144
|
+
* Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
|
|
132
145
|
* Local, HTTP/HTTPS, file-like, and supported compressed sources.
|
|
133
146
|
* CLI and Python API using the same comparison engine.
|
|
134
147
|
|
|
@@ -137,7 +150,7 @@ Record order does not matter.
|
|
|
137
150
|
```text
|
|
138
151
|
jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
139
152
|
[--duplicates {error,first,last}] [--details FILE] [--quiet]
|
|
140
|
-
[--max-temp MAX_TEMP]
|
|
153
|
+
[--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
|
|
141
154
|
old new
|
|
142
155
|
```
|
|
143
156
|
|
|
@@ -147,10 +160,12 @@ jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
|
147
160
|
| `--key KEY` | Required top-level identity field; repeat or comma-separate for composite keys |
|
|
148
161
|
| `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
|
|
149
162
|
| `--where EXPRESSION` | JMESPath filter applied to each record |
|
|
150
|
-
| `--duplicates POLICY` | `error` (default),
|
|
163
|
+
| `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
|
|
151
164
|
| `--details FILE` | Write deterministic machine-readable JSONL changes |
|
|
165
|
+
| `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
|
|
166
|
+
| `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
|
|
152
167
|
| `--quiet` | Suppress the normal summary |
|
|
153
|
-
| `--max-temp BYTES` |
|
|
168
|
+
| `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
|
|
154
169
|
|
|
155
170
|
Examples:
|
|
156
171
|
|
|
@@ -169,18 +184,24 @@ jsonl-diff old.jsonl new.jsonl \
|
|
|
169
184
|
--key id \
|
|
170
185
|
--where 'deleted_at == `null`'
|
|
171
186
|
|
|
187
|
+
# Detect observed schema drift
|
|
188
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
189
|
+
--key id \
|
|
190
|
+
--schema-diff \
|
|
191
|
+
--schema-ignore /metadata
|
|
192
|
+
|
|
172
193
|
# Compressed input
|
|
173
194
|
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
|
|
174
195
|
```
|
|
175
196
|
|
|
176
197
|
### Exit codes
|
|
177
198
|
|
|
178
|
-
| Code | Meaning
|
|
179
|
-
| ---: |
|
|
180
|
-
| `0` |
|
|
181
|
-
| `1` |
|
|
182
|
-
| `2` | Input, resource, output, or runtime error
|
|
183
|
-
| `3` | Invalid CLI configuration or usage
|
|
199
|
+
| Code | Meaning |
|
|
200
|
+
| ---: | ---------------------------------------------------------- |
|
|
201
|
+
| `0` | No record, duplicate, or requested observed-schema issues |
|
|
202
|
+
| `1` | Record differences, tolerated duplicates, or schema changes |
|
|
203
|
+
| `2` | Input, resource, output, or runtime error |
|
|
204
|
+
| `3` | Invalid CLI configuration or usage |
|
|
184
205
|
|
|
185
206
|
## Python API
|
|
186
207
|
|
|
@@ -193,11 +214,15 @@ with diff(
|
|
|
193
214
|
key=("country", "customerId"),
|
|
194
215
|
ignore=("/updated_at",),
|
|
195
216
|
where='country == `"ES"`',
|
|
217
|
+
schema_diff=True,
|
|
196
218
|
) as result:
|
|
197
219
|
print(result.summary)
|
|
198
220
|
|
|
199
221
|
for change in result.changes(ChangeOperation.MODIFIED):
|
|
200
222
|
print(change.key, change.old_line, change.new_line)
|
|
223
|
+
|
|
224
|
+
for change in result.schema_changes():
|
|
225
|
+
print(change.operation, change.path)
|
|
201
226
|
```
|
|
202
227
|
|
|
203
228
|
`diff()` returns a disk-backed `DiffResult`, used as a context manager.
|
|
@@ -220,7 +245,7 @@ The public exception hierarchy is rooted at `JsonlDiffError`:
|
|
|
220
245
|
* `DuplicateKeyError`
|
|
221
246
|
* `ResourceError`
|
|
222
247
|
|
|
223
|
-
See the [Python API reference](docs/python-api.md) for the full callable
|
|
248
|
+
See the [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) for the full callable
|
|
224
249
|
signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
225
250
|
|
|
226
251
|
## Comparison semantics
|
|
@@ -228,18 +253,22 @@ signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
|
228
253
|
`jsonl-diff` is intentionally strict:
|
|
229
254
|
|
|
230
255
|
* Every input line must contain exactly one valid top-level JSON object.
|
|
231
|
-
* Blank lines, malformed JSON,
|
|
256
|
+
* Blank lines, malformed JSON, `NaN`, and infinities are rejected.
|
|
257
|
+
* Duplicate object property names within a record follow JSON's last-wins semantics: the last occurrence is kept.
|
|
232
258
|
* Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
|
|
233
259
|
* Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
|
|
234
|
-
* Duplicate identities fail by default; `--duplicates first`/`last`
|
|
260
|
+
* Duplicate identities fail by default; `--duplicates first`/`last` select one
|
|
261
|
+
occurrence, report every discarded occurrence, and return exit code `1`.
|
|
235
262
|
* Object property order is ignored; array order is significant.
|
|
236
263
|
* Numbers are compared by mathematical value: `1`, `1.0`, and `1e0` are equal.
|
|
237
264
|
* Unicode strings are compared without normalization.
|
|
238
265
|
* Changes are reported in deterministic identity order.
|
|
239
266
|
|
|
240
267
|
`--where` selects which records participate; `--key` defines identity;
|
|
241
|
-
`--ignore` removes fields from content comparison.
|
|
242
|
-
|
|
268
|
+
`--ignore` removes fields from content comparison. With `--schema-diff`,
|
|
269
|
+
`--schema-ignore` independently removes fields from observed-schema profiling.
|
|
270
|
+
See
|
|
271
|
+
[Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) for the full rules,
|
|
243
272
|
including duplicate handling, ignore-pointer edge cases, `--where` evaluation
|
|
244
273
|
order, and canonical number formatting.
|
|
245
274
|
|
|
@@ -255,7 +284,7 @@ Supported compression:
|
|
|
255
284
|
* xz
|
|
256
285
|
* Zstandard on Python 3.14
|
|
257
286
|
|
|
258
|
-
See [architecture](docs/architecture.md) for the full source/compression
|
|
287
|
+
See [architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) for the full source/compression
|
|
259
288
|
support matrix and how sources are delegated to `py-jsonl`.
|
|
260
289
|
|
|
261
290
|
## Limitations
|
|
@@ -268,7 +297,7 @@ support matrix and how sources are delegated to `py-jsonl`.
|
|
|
268
297
|
* nested identities or automatic key detection
|
|
269
298
|
* fuzzy matching or numeric tolerances
|
|
270
299
|
* unordered-array comparison
|
|
271
|
-
* schema
|
|
300
|
+
* validation against a declared schema or input repair
|
|
272
301
|
* ZIP, database, or cloud-provider inputs
|
|
273
302
|
* GUI or HTML reports
|
|
274
303
|
|
|
@@ -276,10 +305,10 @@ It is a **dataset reconciliation tool**, not a general-purpose visual JSON diff.
|
|
|
276
305
|
|
|
277
306
|
## Further reading
|
|
278
307
|
|
|
279
|
-
* [Comparison semantics](docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
280
|
-
* [Disk-backed architecture](docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
281
|
-
* [Details JSONL format](docs/details-format.md) — machine-readable `--details` output schema.
|
|
282
|
-
* [Python API reference](docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
308
|
+
* [Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
309
|
+
* [Disk-backed architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
310
|
+
* [Details JSONL format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md) — machine-readable `--details` output schema.
|
|
311
|
+
* [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
283
312
|
|
|
284
313
|
## Development
|
|
285
314
|
|