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:
equal, added, deleted, modified, and tolerated duplicates in each
source.
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
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
OLD duplicates: 0
NEW duplicates: 0
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 with counts and diagnostics:
error,first, orlast. - Exact RFC 6901 JSON Pointer ignores.
- Optional disk-backed observed-schema diff for fields, types, nullability, and requiredness.
- 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]
[--schema-diff] [--schema-ignore POINTER] [--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), or select and report duplicates with first/last |
--details FILE |
Write deterministic machine-readable JSONL changes |
--schema-diff |
Compare observed fields, types, nullability, and requiredness |
--schema-ignore |
RFC 6901 pointer to exclude from observed-schema profiling |
--quiet |
Suppress the normal summary |
--max-temp BYTES |
Best-effort budget for jsonl-diff workspace 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`'
# Detect observed schema drift
jsonl-diff old.jsonl new.jsonl \
--key id \
--schema-diff \
--schema-ignore /metadata
# Compressed input
jsonl-diff old.jsonl.xz new.jsonl.gz --key id
Exit codes
| Code | Meaning |
|---|---|
0 |
No record, duplicate, or requested observed-schema issues |
1 |
Record differences, tolerated duplicates, or schema changes |
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"`',
schema_diff=True,
) as result:
print(result.summary)
for change in result.changes(ChangeOperation.MODIFIED):
print(change.key, change.old_line, change.new_line)
for change in result.schema_changes():
print(change.operation, change.path)
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/lastselect one occurrence, report every discarded occurrence, and return exit code1. - 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. With --schema-diff,
--schema-ignore independently removes fields from observed-schema profiling.
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
- validation against a declared schema 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.2
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.2.tar.gz | 28.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jsonl_diff-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.2 kB
Release files / jsonl_diff-0.1.2.tar.gz
| Download URL | jsonl_diff-0.1.2.tar.gz |
|---|---|
| Size | 28.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7eceacc1be144d3a39963d87c927a09829ccffbff71b9704aa9a1f763b4397a6
|
|
BLAKE2b-256 checksum How to use checksums |
c9427368a708fe0aeb0ddd02aff4c77ad2114fed29bb96960944a106343927b3
|
| 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.2-py3-none-any.whl
| Download URL | jsonl_diff-0.1.2-py3-none-any.whl |
|---|---|
| Size | 17.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
48c141106d1d37f3cfc72ac994c623b96073559fa0e2846f07763082a9e8e651
|
|
BLAKE2b-256 checksum How to use checksums |
145750627e9d3b81c0f020b48f64b65e3ba5029d6a38c5cfae3f8b4b763db5c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|