jsonl-diff 0.1.0__tar.gz → 0.1.2__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.2/PKG-INFO +310 -0
- jsonl_diff-0.1.2/README.md +255 -0
- jsonl_diff-0.1.2/jsonl_diff.egg-info/PKG-INFO +310 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/jsonl_diff.egg-info/requires.txt +1 -0
- jsonl_diff-0.1.2/jsonl_diff.py +1373 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/pyproject.toml +3 -2
- jsonl_diff-0.1.2/tests/test_cli.py +673 -0
- jsonl_diff-0.1.2/tests/test_diff.py +1100 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/tests/test_sources.py +31 -0
- jsonl_diff-0.1.0/PKG-INFO +0 -548
- jsonl_diff-0.1.0/README.md +0 -494
- jsonl_diff-0.1.0/jsonl_diff.egg-info/PKG-INFO +0 -548
- jsonl_diff-0.1.0/jsonl_diff.py +0 -723
- jsonl_diff-0.1.0/tests/test_cli.py +0 -309
- jsonl_diff-0.1.0/tests/test_diff.py +0 -506
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/LICENSE +0 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/jsonl_diff.egg-info/SOURCES.txt +0 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/jsonl_diff.egg-info/dependency_links.txt +0 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/jsonl_diff.egg-info/entry_points.txt +0 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/jsonl_diff.egg-info/top_level.txt +0 -0
- {jsonl_diff-0.1.0 → jsonl_diff-0.1.2}/setup.cfg +0 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jsonl-diff
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A lightweight, pure-Python tool for comparing large JSONL/NDJSON datasets without loading them entirely into memory.
|
|
5
|
+
Author-email: rmoralespp <rmoralespp@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Rolando Morales Perez
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Source, https://github.com/rmoralespp/jsonl-diff
|
|
29
|
+
Project-URL: Homepage, https://github.com/rmoralespp/jsonl-diff
|
|
30
|
+
Project-URL: Changelog, https://github.com/rmoralespp/jsonl-diff/blob/main/CHANGELOG.md
|
|
31
|
+
Project-URL: Issues, https://github.com/rmoralespp/jsonl-diff/issues
|
|
32
|
+
Project-URL: Documentation, https://rmoralespp.github.io/jsonl-diff/
|
|
33
|
+
Keywords: jsonlines,ndjson,jsonl,json,gzip,bzip2,xz,zst,utilities,serialization,deserialization,diff,json-diff,cli
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
35
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
43
|
+
Classifier: Intended Audience :: Developers
|
|
44
|
+
Classifier: Intended Audience :: Information Technology
|
|
45
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
46
|
+
Classifier: Topic :: Internet
|
|
47
|
+
Classifier: Topic :: Utilities
|
|
48
|
+
Classifier: Topic :: File Formats :: JSON
|
|
49
|
+
Requires-Python: >=3.8
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
Requires-Dist: py-jsonl==1.5.2
|
|
53
|
+
Requires-Dist: jmespath>=1.0.0
|
|
54
|
+
Dynamic: license-file
|
|
55
|
+
|
|
56
|
+
# jsonl-diff
|
|
57
|
+
|
|
58
|
+
<p>
|
|
59
|
+
<img src="https://img.shields.io/pypi/v/jsonl-diff.svg" alt="PyPI">
|
|
60
|
+
<img src="https://img.shields.io/pypi/pyversions/jsonl-diff.svg" alt="Python">
|
|
61
|
+
<img src="https://github.com/rmoralespp/jsonl-diff/workflows/CI/badge.svg" alt="CI">
|
|
62
|
+
<img src="https://codecov.io/gh/rmoralespp/jsonl-diff/branch/main/graph/badge.svg" alt="Coverage">
|
|
63
|
+
<img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
Compare large JSONL/NDJSON datasets **by record identity instead of line
|
|
67
|
+
position**, without loading the complete inputs into memory.
|
|
68
|
+
|
|
69
|
+
`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.
|
|
72
|
+
|
|
73
|
+
It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
74
|
+
|
|
75
|
+
> For line-level, field-level, JSON Patch, or visual diffs, use a tool designed for those purposes instead.
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install jsonl-diff
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
Given:
|
|
86
|
+
|
|
87
|
+
```jsonl
|
|
88
|
+
{"id":1,"name":"Ada"}
|
|
89
|
+
{"id":2,"name":"Grace"}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
and:
|
|
93
|
+
|
|
94
|
+
```jsonl
|
|
95
|
+
{"id":2,"name":"Grace Hopper"}
|
|
96
|
+
{"name":"Ada","id":1}
|
|
97
|
+
{"id":3,"name":"Linus"}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
compare them by `id`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
jsonl-diff old.jsonl new.jsonl --key id
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
Records:
|
|
108
|
+
equal: 1
|
|
109
|
+
added: 1
|
|
110
|
+
deleted: 0
|
|
111
|
+
modified: 1
|
|
112
|
+
OLD duplicates: 0
|
|
113
|
+
NEW duplicates: 0
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Record order does not matter.
|
|
117
|
+
|
|
118
|
+
## Features
|
|
119
|
+
|
|
120
|
+
* Single or composite top-level identities.
|
|
121
|
+
* Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
|
|
122
|
+
* Optional **JMESPath** filtering with `--where`.
|
|
123
|
+
* Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
|
|
124
|
+
* Exact **RFC 6901** JSON Pointer ignores.
|
|
125
|
+
* Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
|
|
126
|
+
* Semantic number comparison using `Decimal`.
|
|
127
|
+
* Deterministic summaries and change iteration.
|
|
128
|
+
* Original OLD/NEW physical line numbers.
|
|
129
|
+
* Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
|
|
130
|
+
* Local, HTTP/HTTPS, file-like, and supported compressed sources.
|
|
131
|
+
* CLI and Python API using the same comparison engine.
|
|
132
|
+
|
|
133
|
+
## CLI
|
|
134
|
+
|
|
135
|
+
```text
|
|
136
|
+
jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
137
|
+
[--duplicates {error,first,last}] [--details FILE] [--quiet]
|
|
138
|
+
[--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
|
|
139
|
+
old new
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
| Option | Description |
|
|
143
|
+
| --------------------- | ------------------------------------------------------------------------------ |
|
|
144
|
+
| `old`, `new` | Local path, HTTP/HTTPS source, or `-` for stdin |
|
|
145
|
+
| `--key KEY` | Required top-level identity field; repeat or comma-separate for composite keys |
|
|
146
|
+
| `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
|
|
147
|
+
| `--where EXPRESSION` | JMESPath filter applied to each record |
|
|
148
|
+
| `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
|
|
149
|
+
| `--details FILE` | Write deterministic machine-readable JSONL changes |
|
|
150
|
+
| `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
|
|
151
|
+
| `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
|
|
152
|
+
| `--quiet` | Suppress the normal summary |
|
|
153
|
+
| `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
|
|
154
|
+
|
|
155
|
+
Examples:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Composite identity
|
|
159
|
+
jsonl-diff old.jsonl new.jsonl --key country,customerId,type
|
|
160
|
+
|
|
161
|
+
# Ignore volatile fields
|
|
162
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
163
|
+
--key id \
|
|
164
|
+
--ignore /updated_at \
|
|
165
|
+
--ignore /metadata/request_id
|
|
166
|
+
|
|
167
|
+
# Filter records
|
|
168
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
169
|
+
--key id \
|
|
170
|
+
--where 'deleted_at == `null`'
|
|
171
|
+
|
|
172
|
+
# Detect observed schema drift
|
|
173
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
174
|
+
--key id \
|
|
175
|
+
--schema-diff \
|
|
176
|
+
--schema-ignore /metadata
|
|
177
|
+
|
|
178
|
+
# Compressed input
|
|
179
|
+
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Exit codes
|
|
183
|
+
|
|
184
|
+
| Code | Meaning |
|
|
185
|
+
| ---: | ---------------------------------------------------------- |
|
|
186
|
+
| `0` | No record, duplicate, or requested observed-schema issues |
|
|
187
|
+
| `1` | Record differences, tolerated duplicates, or schema changes |
|
|
188
|
+
| `2` | Input, resource, output, or runtime error |
|
|
189
|
+
| `3` | Invalid CLI configuration or usage |
|
|
190
|
+
|
|
191
|
+
## Python API
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from jsonl_diff import ChangeOperation, diff
|
|
195
|
+
|
|
196
|
+
with diff(
|
|
197
|
+
"old.jsonl.gz",
|
|
198
|
+
"new.jsonl.gz",
|
|
199
|
+
key=("country", "customerId"),
|
|
200
|
+
ignore=("/updated_at",),
|
|
201
|
+
where='country == `"ES"`',
|
|
202
|
+
schema_diff=True,
|
|
203
|
+
) as result:
|
|
204
|
+
print(result.summary)
|
|
205
|
+
|
|
206
|
+
for change in result.changes(ChangeOperation.MODIFIED):
|
|
207
|
+
print(change.key, change.old_line, change.new_line)
|
|
208
|
+
|
|
209
|
+
for change in result.schema_changes():
|
|
210
|
+
print(change.operation, change.path)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`diff()` returns a disk-backed `DiffResult`, used as a context manager.
|
|
214
|
+
Results are streamed lazily through `changes()` rather than materialized in
|
|
215
|
+
memory.
|
|
216
|
+
|
|
217
|
+
The main result models are:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
Summary(equal=10, added=2, deleted=1, modified=3)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
and immutable `Change` objects containing the operation, typed identity, and
|
|
224
|
+
OLD/NEW source locations.
|
|
225
|
+
|
|
226
|
+
The public exception hierarchy is rooted at `JsonlDiffError`:
|
|
227
|
+
|
|
228
|
+
* `ConfigurationError`
|
|
229
|
+
* `InputError`
|
|
230
|
+
* `DuplicateKeyError`
|
|
231
|
+
* `ResourceError`
|
|
232
|
+
|
|
233
|
+
See the [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) for the full callable
|
|
234
|
+
signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
235
|
+
|
|
236
|
+
## Comparison semantics
|
|
237
|
+
|
|
238
|
+
`jsonl-diff` is intentionally strict:
|
|
239
|
+
|
|
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.
|
|
242
|
+
* Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
|
|
243
|
+
* Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
|
|
244
|
+
* Duplicate identities fail by default; `--duplicates first`/`last` select one
|
|
245
|
+
occurrence, report every discarded occurrence, and return exit code `1`.
|
|
246
|
+
* Object property order is ignored; array order is significant.
|
|
247
|
+
* Numbers are compared by mathematical value: `1`, `1.0`, and `1e0` are equal.
|
|
248
|
+
* Unicode strings are compared without normalization.
|
|
249
|
+
* Changes are reported in deterministic identity order.
|
|
250
|
+
|
|
251
|
+
`--where` selects which records participate; `--key` defines identity;
|
|
252
|
+
`--ignore` removes fields from content comparison. With `--schema-diff`,
|
|
253
|
+
`--schema-ignore` independently removes fields from observed-schema profiling.
|
|
254
|
+
See
|
|
255
|
+
[Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) for the full rules,
|
|
256
|
+
including duplicate handling, ignore-pointer edge cases, `--where` evaluation
|
|
257
|
+
order, and canonical number formatting.
|
|
258
|
+
|
|
259
|
+
## Sources & compression
|
|
260
|
+
|
|
261
|
+
Supported sources include local paths and HTTP/HTTPS URLs. The Python API
|
|
262
|
+
also accepts file-like objects.
|
|
263
|
+
|
|
264
|
+
Supported compression:
|
|
265
|
+
|
|
266
|
+
* gzip
|
|
267
|
+
* bzip2
|
|
268
|
+
* xz
|
|
269
|
+
* Zstandard on Python 3.14
|
|
270
|
+
|
|
271
|
+
See [architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) for the full source/compression
|
|
272
|
+
support matrix and how sources are delegated to `py-jsonl`.
|
|
273
|
+
|
|
274
|
+
## Limitations
|
|
275
|
+
|
|
276
|
+
`jsonl-diff` does **not** provide:
|
|
277
|
+
|
|
278
|
+
* line/position-based diffs
|
|
279
|
+
* field-level diffs or JSON Patch
|
|
280
|
+
* move/rename detection
|
|
281
|
+
* nested identities or automatic key detection
|
|
282
|
+
* fuzzy matching or numeric tolerances
|
|
283
|
+
* unordered-array comparison
|
|
284
|
+
* validation against a declared schema or input repair
|
|
285
|
+
* ZIP, database, or cloud-provider inputs
|
|
286
|
+
* GUI or HTML reports
|
|
287
|
+
|
|
288
|
+
It is a **dataset reconciliation tool**, not a general-purpose visual JSON diff.
|
|
289
|
+
|
|
290
|
+
## Further reading
|
|
291
|
+
|
|
292
|
+
* [Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
293
|
+
* [Disk-backed architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
294
|
+
* [Details JSONL format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md) — machine-readable `--details` output schema.
|
|
295
|
+
* [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
296
|
+
|
|
297
|
+
## Development
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
uv sync --group test --group lint
|
|
301
|
+
|
|
302
|
+
uv run pytest
|
|
303
|
+
uv run ruff check --quiet --output-format=concise .
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The test suite covers CLI behavior, identity/canonicalization, validation, details output, sources, compression, temporary limits, and cleanup.
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# jsonl-diff
|
|
2
|
+
|
|
3
|
+
<p>
|
|
4
|
+
<img src="https://img.shields.io/pypi/v/jsonl-diff.svg" alt="PyPI">
|
|
5
|
+
<img src="https://img.shields.io/pypi/pyversions/jsonl-diff.svg" alt="Python">
|
|
6
|
+
<img src="https://github.com/rmoralespp/jsonl-diff/workflows/CI/badge.svg" alt="CI">
|
|
7
|
+
<img src="https://codecov.io/gh/rmoralespp/jsonl-diff/branch/main/graph/badge.svg" alt="Coverage">
|
|
8
|
+
<img src="https://img.shields.io/github/license/rmoralespp/jsonl-diff.svg" alt="License">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
Compare large JSONL/NDJSON datasets **by record identity instead of line
|
|
12
|
+
position**, without loading the complete inputs into memory.
|
|
13
|
+
|
|
14
|
+
`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.
|
|
17
|
+
|
|
18
|
+
It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
|
|
19
|
+
|
|
20
|
+
> For line-level, field-level, JSON Patch, or visual diffs, use a tool designed for those purposes instead.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install jsonl-diff
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
Given:
|
|
31
|
+
|
|
32
|
+
```jsonl
|
|
33
|
+
{"id":1,"name":"Ada"}
|
|
34
|
+
{"id":2,"name":"Grace"}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
and:
|
|
38
|
+
|
|
39
|
+
```jsonl
|
|
40
|
+
{"id":2,"name":"Grace Hopper"}
|
|
41
|
+
{"name":"Ada","id":1}
|
|
42
|
+
{"id":3,"name":"Linus"}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
compare them by `id`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
jsonl-diff old.jsonl new.jsonl --key id
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
Records:
|
|
53
|
+
equal: 1
|
|
54
|
+
added: 1
|
|
55
|
+
deleted: 0
|
|
56
|
+
modified: 1
|
|
57
|
+
OLD duplicates: 0
|
|
58
|
+
NEW duplicates: 0
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Record order does not matter.
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
* Single or composite top-level identities.
|
|
66
|
+
* Disk-backed comparison for large datasets ([architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md)).
|
|
67
|
+
* Optional **JMESPath** filtering with `--where`.
|
|
68
|
+
* Configurable duplicate handling with counts and diagnostics: `error`, `first`, or `last`.
|
|
69
|
+
* Exact **RFC 6901** JSON Pointer ignores.
|
|
70
|
+
* Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
|
|
71
|
+
* Semantic number comparison using `Decimal`.
|
|
72
|
+
* Deterministic summaries and change iteration.
|
|
73
|
+
* Original OLD/NEW physical line numbers.
|
|
74
|
+
* Machine-readable JSONL change log with `--details` ([format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md)).
|
|
75
|
+
* Local, HTTP/HTTPS, file-like, and supported compressed sources.
|
|
76
|
+
* CLI and Python API using the same comparison engine.
|
|
77
|
+
|
|
78
|
+
## CLI
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
|
|
82
|
+
[--duplicates {error,first,last}] [--details FILE] [--quiet]
|
|
83
|
+
[--schema-diff] [--schema-ignore POINTER] [--max-temp MAX_TEMP]
|
|
84
|
+
old new
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Option | Description |
|
|
88
|
+
| --------------------- | ------------------------------------------------------------------------------ |
|
|
89
|
+
| `old`, `new` | Local path, HTTP/HTTPS source, or `-` for stdin |
|
|
90
|
+
| `--key KEY` | Required top-level identity field; repeat or comma-separate for composite keys |
|
|
91
|
+
| `--ignore POINTER` | RFC 6901 pointer to exclude from content comparison |
|
|
92
|
+
| `--where EXPRESSION` | JMESPath filter applied to each record |
|
|
93
|
+
| `--duplicates POLICY` | `error` (default), or select and report duplicates with `first`/`last` |
|
|
94
|
+
| `--details FILE` | Write deterministic machine-readable JSONL changes |
|
|
95
|
+
| `--schema-diff` | Compare observed fields, types, nullability, and requiredness |
|
|
96
|
+
| `--schema-ignore` | RFC 6901 pointer to exclude from observed-schema profiling |
|
|
97
|
+
| `--quiet` | Suppress the normal summary |
|
|
98
|
+
| `--max-temp BYTES` | Best-effort budget for `jsonl-diff` workspace temporary storage |
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Composite identity
|
|
104
|
+
jsonl-diff old.jsonl new.jsonl --key country,customerId,type
|
|
105
|
+
|
|
106
|
+
# Ignore volatile fields
|
|
107
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
108
|
+
--key id \
|
|
109
|
+
--ignore /updated_at \
|
|
110
|
+
--ignore /metadata/request_id
|
|
111
|
+
|
|
112
|
+
# Filter records
|
|
113
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
114
|
+
--key id \
|
|
115
|
+
--where 'deleted_at == `null`'
|
|
116
|
+
|
|
117
|
+
# Detect observed schema drift
|
|
118
|
+
jsonl-diff old.jsonl new.jsonl \
|
|
119
|
+
--key id \
|
|
120
|
+
--schema-diff \
|
|
121
|
+
--schema-ignore /metadata
|
|
122
|
+
|
|
123
|
+
# Compressed input
|
|
124
|
+
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Exit codes
|
|
128
|
+
|
|
129
|
+
| Code | Meaning |
|
|
130
|
+
| ---: | ---------------------------------------------------------- |
|
|
131
|
+
| `0` | No record, duplicate, or requested observed-schema issues |
|
|
132
|
+
| `1` | Record differences, tolerated duplicates, or schema changes |
|
|
133
|
+
| `2` | Input, resource, output, or runtime error |
|
|
134
|
+
| `3` | Invalid CLI configuration or usage |
|
|
135
|
+
|
|
136
|
+
## Python API
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from jsonl_diff import ChangeOperation, diff
|
|
140
|
+
|
|
141
|
+
with diff(
|
|
142
|
+
"old.jsonl.gz",
|
|
143
|
+
"new.jsonl.gz",
|
|
144
|
+
key=("country", "customerId"),
|
|
145
|
+
ignore=("/updated_at",),
|
|
146
|
+
where='country == `"ES"`',
|
|
147
|
+
schema_diff=True,
|
|
148
|
+
) as result:
|
|
149
|
+
print(result.summary)
|
|
150
|
+
|
|
151
|
+
for change in result.changes(ChangeOperation.MODIFIED):
|
|
152
|
+
print(change.key, change.old_line, change.new_line)
|
|
153
|
+
|
|
154
|
+
for change in result.schema_changes():
|
|
155
|
+
print(change.operation, change.path)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`diff()` returns a disk-backed `DiffResult`, used as a context manager.
|
|
159
|
+
Results are streamed lazily through `changes()` rather than materialized in
|
|
160
|
+
memory.
|
|
161
|
+
|
|
162
|
+
The main result models are:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
Summary(equal=10, added=2, deleted=1, modified=3)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
and immutable `Change` objects containing the operation, typed identity, and
|
|
169
|
+
OLD/NEW source locations.
|
|
170
|
+
|
|
171
|
+
The public exception hierarchy is rooted at `JsonlDiffError`:
|
|
172
|
+
|
|
173
|
+
* `ConfigurationError`
|
|
174
|
+
* `InputError`
|
|
175
|
+
* `DuplicateKeyError`
|
|
176
|
+
* `ResourceError`
|
|
177
|
+
|
|
178
|
+
See the [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) for the full callable
|
|
179
|
+
signature, result models, `Decimal` key semantics, and error hierarchy.
|
|
180
|
+
|
|
181
|
+
## Comparison semantics
|
|
182
|
+
|
|
183
|
+
`jsonl-diff` is intentionally strict:
|
|
184
|
+
|
|
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.
|
|
187
|
+
* Identity fields must be top-level scalar values (`null` allowed only as one component of a composite key).
|
|
188
|
+
* Identity types remain significant: `"1"` ≠ `1`, `true` ≠ `1`.
|
|
189
|
+
* Duplicate identities fail by default; `--duplicates first`/`last` select one
|
|
190
|
+
occurrence, report every discarded occurrence, and return exit code `1`.
|
|
191
|
+
* Object property order is ignored; array order is significant.
|
|
192
|
+
* Numbers are compared by mathematical value: `1`, `1.0`, and `1e0` are equal.
|
|
193
|
+
* Unicode strings are compared without normalization.
|
|
194
|
+
* Changes are reported in deterministic identity order.
|
|
195
|
+
|
|
196
|
+
`--where` selects which records participate; `--key` defines identity;
|
|
197
|
+
`--ignore` removes fields from content comparison. With `--schema-diff`,
|
|
198
|
+
`--schema-ignore` independently removes fields from observed-schema profiling.
|
|
199
|
+
See
|
|
200
|
+
[Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) for the full rules,
|
|
201
|
+
including duplicate handling, ignore-pointer edge cases, `--where` evaluation
|
|
202
|
+
order, and canonical number formatting.
|
|
203
|
+
|
|
204
|
+
## Sources & compression
|
|
205
|
+
|
|
206
|
+
Supported sources include local paths and HTTP/HTTPS URLs. The Python API
|
|
207
|
+
also accepts file-like objects.
|
|
208
|
+
|
|
209
|
+
Supported compression:
|
|
210
|
+
|
|
211
|
+
* gzip
|
|
212
|
+
* bzip2
|
|
213
|
+
* xz
|
|
214
|
+
* Zstandard on Python 3.14
|
|
215
|
+
|
|
216
|
+
See [architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) for the full source/compression
|
|
217
|
+
support matrix and how sources are delegated to `py-jsonl`.
|
|
218
|
+
|
|
219
|
+
## Limitations
|
|
220
|
+
|
|
221
|
+
`jsonl-diff` does **not** provide:
|
|
222
|
+
|
|
223
|
+
* line/position-based diffs
|
|
224
|
+
* field-level diffs or JSON Patch
|
|
225
|
+
* move/rename detection
|
|
226
|
+
* nested identities or automatic key detection
|
|
227
|
+
* fuzzy matching or numeric tolerances
|
|
228
|
+
* unordered-array comparison
|
|
229
|
+
* validation against a declared schema or input repair
|
|
230
|
+
* ZIP, database, or cloud-provider inputs
|
|
231
|
+
* GUI or HTML reports
|
|
232
|
+
|
|
233
|
+
It is a **dataset reconciliation tool**, not a general-purpose visual JSON diff.
|
|
234
|
+
|
|
235
|
+
## Further reading
|
|
236
|
+
|
|
237
|
+
* [Comparison semantics](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/comparison-semantics.md) — identity, duplicates, ignores, `--where`, canonical numbers, determinism.
|
|
238
|
+
* [Disk-backed architecture](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/architecture.md) — SQLite index, `max_temp`, cleanup, sources and compression.
|
|
239
|
+
* [Details JSONL format](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/details-format.md) — machine-readable `--details` output schema.
|
|
240
|
+
* [Python API reference](https://github.com/rmoralespp/jsonl-diff/blob/main/docs/python-api.md) — full signature, result models, and error hierarchy.
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
uv sync --group test --group lint
|
|
246
|
+
|
|
247
|
+
uv run pytest
|
|
248
|
+
uv run ruff check --quiet --output-format=concise .
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The test suite covers CLI behavior, identity/canonicalization, validation, details output, sources, compression, temporary limits, and cleanup.
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
See [LICENSE](LICENSE).
|