jsonl-diff
Compare large JSONL/NDJSON datasets by record identity instead of line position, without loading the complete inputs into memory.
jsonl-diff matches records using one or more top-level fields and reports:
equaladdeddeletedmodified
It is useful for snapshots, ETL validation, migrations, exports, and CI checks.
For line-level, field-level, JSON Patch, or visual diffs, use a tool designed for those purposes instead.
Installation
Python 3.8–3.14.
python -m pip install jsonl-diff
Quick start
Given:
{"id":1,"name":"Ada"}
{"id":2,"name":"Grace"}
and:
{"id":2,"name":"Grace Hopper"}
{"name":"Ada","id":1}
{"id":3,"name":"Linus"}
compare them by id:
jsonl-diff old.jsonl new.jsonl --key id
Records:
equal: 1
added: 1
deleted: 0
modified: 1
Record order does not matter.
Features
- Single or composite top-level identities.
- Disk-backed comparison for large datasets (architecture).
- Optional JMESPath filtering with
--where. - Configurable duplicate handling:
error,first, orlast. - Exact RFC 6901 JSON Pointer ignores.
- Semantic number comparison using
Decimal. - Deterministic summaries and change iteration.
- Original OLD/NEW physical line numbers.
- Machine-readable JSONL change log with
--details(format). - Local, HTTP/HTTPS, file-like, and supported compressed sources.
- CLI and Python API using the same comparison engine.
CLI
jsonl-diff [-h] --key KEY [--ignore IGNORE] [--where EXPRESSION]
[--duplicates {error,first,last}] [--details FILE] [--quiet]
[--max-temp MAX_TEMP]
old new
| Option | Description |
|---|---|
old, new |
Local path, HTTP/HTTPS source, or - for stdin |
--key KEY |
Required top-level identity field; repeat or comma-separate for composite keys |
--ignore POINTER |
RFC 6901 pointer to exclude from content comparison |
--where EXPRESSION |
JMESPath filter applied to each record |
--duplicates POLICY |
error (default), first, or last |
--details FILE |
Write deterministic machine-readable JSONL changes |
--quiet |
Suppress the normal summary |
--max-temp BYTES |
Limit jsonl-diff temporary storage |
Examples:
# Composite identity
jsonl-diff old.jsonl new.jsonl --key country,customerId,type
# Ignore volatile fields
jsonl-diff old.jsonl new.jsonl \
--key id \
--ignore /updated_at \
--ignore /metadata/request_id
# Filter records
jsonl-diff old.jsonl new.jsonl \
--key id \
--where 'deleted_at == `null`'
# Compressed input
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
Exit codes
| Code | Meaning |
|---|---|
0 |
Inputs are equal |
1 |
Added, deleted, or modified records found |
2 |
Input, resource, output, or runtime error |
3 |
Invalid CLI configuration or usage |
Python API
from jsonl_diff import ChangeOperation, diff
with diff(
"old.jsonl.gz",
"new.jsonl.gz",
key=("country", "customerId"),
ignore=("/updated_at",),
where='country == `"ES"`',
) as result:
print(result.summary)
for change in result.changes(ChangeOperation.MODIFIED):
print(change.key, change.old_line, change.new_line)
diff() returns a disk-backed DiffResult, used as a context manager.
Results are streamed lazily through changes() rather than materialized in
memory.
The main result models are:
Summary(equal=10, added=2, deleted=1, modified=3)
and immutable Change objects containing the operation, typed identity, and
OLD/NEW source locations.
The public exception hierarchy is rooted at JsonlDiffError:
ConfigurationErrorInputErrorDuplicateKeyErrorResourceError
See the Python API reference for the full callable
signature, result models, Decimal key semantics, and error hierarchy.
Comparison semantics
jsonl-diff is intentionally strict:
- Every input line must contain exactly one valid top-level JSON object.
- Blank lines, malformed JSON, duplicate properties,
NaN, and infinities are rejected. - Identity fields must be top-level scalar values (
nullallowed only as one component of a composite key). - Identity types remain significant:
"1"≠1,true≠1. - Duplicate identities fail by default;
--duplicates first/lasttolerate them. - Object property order is ignored; array order is significant.
- Numbers are compared by mathematical value:
1,1.0, and1e0are equal. - Unicode strings are compared without normalization.
- Changes are reported in deterministic identity order.
--where selects which records participate; --key defines identity;
--ignore removes fields from content comparison. See
Comparison semantics for the full rules,
including duplicate handling, ignore-pointer edge cases, --where evaluation
order, and canonical number formatting.
Sources & compression
Supported sources include local paths and HTTP/HTTPS URLs. The Python API also accepts file-like objects.
Supported compression:
- gzip
- bzip2
- xz
- Zstandard on Python 3.14
See architecture for the full source/compression
support matrix and how sources are delegated to py-jsonl.
Limitations
jsonl-diff does not provide:
- line/position-based diffs
- field-level diffs or JSON Patch
- move/rename detection
- nested identities or automatic key detection
- fuzzy matching or numeric tolerances
- unordered-array comparison
- schema validation or input repair
- ZIP, database, or cloud-provider inputs
- GUI or HTML reports
It is a dataset reconciliation tool, not a general-purpose visual JSON diff.
Further reading
- Comparison semantics — identity, duplicates, ignores,
--where, canonical numbers, determinism. - Disk-backed architecture — SQLite index,
max_temp, cleanup, sources and compression. - Details JSONL format — machine-readable
--detailsoutput schema. - Python API reference — full signature, result models, and error hierarchy.
Development
uv sync --group test --group lint
uv run pytest
uv run ruff check --quiet --output-format=concise .
The test suite covers CLI behavior, identity/canonicalization, validation, details output, sources, compression, temporary limits, and cleanup.
License
See LICENSE.
Release files for jsonl-diff 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jsonl_diff-0.1.1.tar.gz | 21.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jsonl_diff-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.9 kB
Release files / jsonl_diff-0.1.1.tar.gz
| Download URL | jsonl_diff-0.1.1.tar.gz |
|---|---|
| Size | 21.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2ebadd8a2271db0c1cb734a7749474c93752169c11c2da84cbda3b425d24b6ea
|
|
BLAKE2b-256 checksum How to use checksums |
8b2d2691d9fcf1fbd352f69066216a7f5155fea8fbbe83708d8996d61bfb69b8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / jsonl_diff-0.1.1-py3-none-any.whl
| Download URL | jsonl_diff-0.1.1-py3-none-any.whl |
|---|---|
| Size | 13.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
80c98567eafd665d056a31f0970f30968f42a58b07fca381e8d056fc16f91d2e
|
|
BLAKE2b-256 checksum How to use checksums |
75a1677dd3761258a593381a88e282559c1e4b47afdf442bd7f5250b79c64893
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|