Diff two JSON files and produce a modern, self-contained, themeable HTML report. No runtime dependencies.
Project description
jsondiffhtml
The fastest way to turn two JSON files into a shareable, reviewable HTML diff report.
jsondiffhtml reads two JSON files, deep-diffs them (dicts, lists, primitives, and unordered lists of objects), and writes a single, self-contained HTML report you can email, commit, attach to a ticket, or host anywhere. No CDN calls, no external stylesheets, no runtime dependencies.
pip install jsondiffhtml
json-diff before.json after.json -o report.html
Why jsondiffhtml?
Most Python JSON-diff libraries hand you back a Python dict. That's useful if you're writing a program — useless if you're the reviewer on a pull request, the engineer debugging a broken deploy, or the ops person approving a config change. Those people want a diff they can read.
| jsondiffhtml | deepdiff | xlwings/jsondiff | |
|---|---|---|---|
| Output format | HTML report (self-contained) | Python dict | Python dict |
| Grouping by JSON path | ✅ | ❌ | ❌ |
| Searchable UI with filter tabs | ✅ | ❌ | ❌ |
| Source line numbers per diff | ✅ | ❌ | ❌ |
| Per-item ignore & comment boxes | ✅ | ❌ | ❌ |
| Identifier-based list matching | ✅ | partial | partial |
| Include/exclude path filters | ✅ | ✅ | ✅ |
| Themeable (color, title, logo) | ✅ | n/a | n/a |
| Auto-splits very large reports | ✅ | n/a | n/a |
| CLI included | ✅ (json-diff) |
❌ | ✅ (jdiff) |
| Runtime dependencies | 0 | 1 (orderly-set) |
0 |
| Python 3.9+ | ✅ | ✅ | ✅ |
Use jsondiffhtml when you need a human-readable diff artifact. Use deepdiff or xlwings/jsondiff when you need a programmatic dict to drive application logic.
Quick start — CLI
# minimal
json-diff before.json after.json -o report.html
# themed, with a logo in the sidebar
json-diff before.json after.json -o report.html \
--title "Release 2.4 config diff" \
--primary-color "#16a34a" \
--logo-url https://example.com/logo.svg
# filter to one subtree
json-diff before.json after.json -o report.html \
--include-path "root['services']" \
--exclude-path "root['services']['internal']"
Open report.html in a browser. Everything is inline — you can attach it to an email, host it on a static bucket, or drop it into a Slack thread.
Quick start — Python
from jsondiffhtml import JsonDiff, HtmlReporter, ReportConfig
diff = JsonDiff.from_files("before.json", "after.json")
print(f"{len(diff.objects_changed)} changed, "
f"{len(diff.objects_added)} added, "
f"{len(diff.objects_removed)} removed")
HtmlReporter(
diff,
ReportConfig(title="Release 2.4 config diff", primary_color="#16a34a"),
).write("report.html")
Each item in diff.objects_changed, diff.objects_added, diff.objects_removed is a frozen dataclass carrying the source path, destination path, old/new values, source-line numbers, and a human-readable label (see ListIdentifier below).
Matching unordered list items by identifier
When your JSON contains lists of objects whose order doesn't matter (users, resources, records), tell jsondiffhtml which key identifies each item so a reordered-but-unchanged list doesn't flood your report with false positives.
from jsondiffhtml import JsonDiff, HtmlReporter, ListIdentifier
diff = JsonDiff(
before,
after,
identifiers_list=[
ListIdentifier(
parent_path="root['users']",
id_key_name="id",
get_identifier_value_func=lambda u: f"{u['first_name']} {u['last_name']}",
),
],
)
HtmlReporter(diff).write("users_diff.html")
parent_pathis the path to the list with[<int>]indices stripped (root['users'], notroot['users'][0]).get_identifier_value_funcis optional. If set, its return value becomes the readable label shown on each diff row — so reviewers see "Jane Doe" instead ofroot['users'][17].
Filtering
JsonDiff(
a, b,
included_paths=["root['config']"], # keep only diffs whose path contains this
excluded_paths=["root['config']['secret']"], # drop diffs whose (index-stripped) path matches exactly
)
Large reports
When the total item count exceeds ReportConfig.split_threshold, write multiple files instead of one massive HTML:
HtmlReporter(diff, ReportConfig(split_threshold=50_000, max_items_per_file=30_000)) \
.write_split("out_dir", "report")
# Produces: out_dir/report_changed_0.html, _added_0.html, _removed_0.html, ...
ReportConfig reference
| Field | Default | Purpose |
|---|---|---|
title |
"JSON Diff Report" |
Page title and sidebar heading |
logo_url |
None |
Optional image shown above the title |
primary_color |
"#2563eb" |
Any CSS color — drives the theme via CSS variables |
group_results |
True |
True = group items by path; False = flat chronological list |
show_line_numbers |
True |
Annotate paths with (line N) |
include_annotations |
True |
Render per-item Ignore checkbox and comment textarea |
split_threshold |
50_000 |
When write_split() sees more items than this, splits per-category |
max_items_per_file |
30_000 |
Chunk size when splitting |
Python API reference
from jsondiffhtml import (
JsonDiff, # core diff engine
ListIdentifier, # identifier-based list matching
ReportConfig, # report customization
HtmlReporter, # HTML generation
AddedObj, ChangedObj, RemovedObj, # result dataclasses
JsonPathLineNumberMapper, # path → line number lookup (used internally; public for advanced use)
)
JsonDiff constructors:
JsonDiff(obj_a, obj_b, identifiers_list=None, included_paths=None, excluded_paths=None)— from already-parsed Python objects.JsonDiff.from_files(path_a, path_b, **kwargs)— load JSON from disk.
HtmlReporter methods:
render() -> str— return the full HTML document as a string.write(path) -> Path— write a single-file report.write_split(dir, basename) -> list[Path]— auto-split on large reports.
CLI reference
json-diff [-h] [-o OUTPUT] [--title TITLE] [--primary-color COLOR]
[--logo-url URL] [--flat] [--no-annotations] [--no-line-numbers]
[--include-path PATH] [--exclude-path PATH] [--version]
file_a file_b
Development
git clone https://github.com/OWNER/jsondiffhtml && cd jsondiffhtml
pip install -e ".[dev]"
pytest # 32 tests
ruff check src/
mypy src/jsondiffhtml
python -m build # wheel + sdist
Changelog
See CHANGELOG.md.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jsondiffhtml-0.1.0.tar.gz.
File metadata
- Download URL: jsondiffhtml-0.1.0.tar.gz
- Upload date:
- Size: 27.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
33b0c4e060d259b0c2299ecb9f7166c4b3299f1742436732d8ae261c403fbb3c
|
|
| MD5 |
2b37c142f3b06e6865753e5452b8da22
|
|
| BLAKE2b-256 |
2cbbebb35aecb560091f404e51d695f7417a17b77e0fd9b4d1a0c3f3a13a03d8
|
File details
Details for the file jsondiffhtml-0.1.0-py3-none-any.whl.
File metadata
- Download URL: jsondiffhtml-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a124e5b055788eb52cdb4c56b653d732b6da57b91e8a9d46297300ffefe99be0
|
|
| MD5 |
479e72ae73fe1a753e4a91dd9de9b6b1
|
|
| BLAKE2b-256 |
53789129140280617576f70f197fe8d8b3f2bbdc1fe502b011fe6688cb22f33f
|