philiprehberger-json-diff
Readable JSON comparison with colorized terminal output.
Installation
pip install philiprehberger-json-diff
Usage
from philiprehberger_json_diff import diff, format_diff, diff_summary
old = {"name": "Alice", "age": 30, "city": "NYC"}
new = {"name": "Alice", "age": 31, "country": "US"}
changes = diff(old, new)
# Pretty-print with colors
print(format_diff(changes))
# Get a summary
summary = diff_summary(changes)
# {"added": 1, "removed": 1, "modified": 1, "unchanged": 0}
Nested Comparison
from philiprehberger_json_diff import diff
old = {"user": {"name": "Alice", "settings": {"theme": "dark"}}}
new = {"user": {"name": "Alice", "settings": {"theme": "light"}}}
changes = diff(old, new)
# Reports: modified user.settings.theme: "dark" -> "light"
Ignore Paths
from philiprehberger_json_diff import diff
changes = diff(old, new, ignore={"user.settings.theme"})
Wildcard Ignore Patterns
from philiprehberger_json_diff import diff
old = {"a": {"metadata": {"ts": 1}}, "b": {"metadata": {"ts": 2}}}
new = {"a": {"metadata": {"ts": 99}}, "b": {"metadata": {"ts": 99}}}
# Ignore metadata fields at any depth
changes = diff(old, new, ignore={"*.metadata.*"})
Filter by paths
from philiprehberger_json_diff import diff_paths
old = {
"users": {"alice": {"email": "a@old.com"}, "bob": {"email": "b@old.com"}},
"config": {"timeout": 30, "retries": 3},
}
new = {
"users": {"alice": {"email": "a@new.com"}, "bob": {"email": "b@new.com"}},
"config": {"timeout": 60, "retries": 3},
}
# Watch only specific subtrees — wildcards work just like ignore patterns
changes = diff_paths(old, new, ["users.*.email", "config.timeout"])
# Reports: users.alice.email, users.bob.email, config.timeout
Structural Diff Mode
from philiprehberger_json_diff import diff
old = {"a": 1, "b": "hello", "c": 3}
new = {"a": "one", "b": "world", "d": 4}
result = diff(old, new, mode="structural")
result.key_additions # [Change(path='d', ...)]
result.key_removals # [Change(path='c', ...)]
result.value_changes # [Change(path='b', ...)]
result.type_changes # [Change(path='a', ...)]
Apply Patch
from philiprehberger_json_diff import diff, apply_patch
old = {"name": "Alice", "age": 30}
new = {"name": "Bob", "age": 31}
changes = diff(old, new)
result = apply_patch(old, changes)
# result == {"name": "Bob", "age": 31}
JSON Patch (RFC 6902)
from philiprehberger_json_diff import diff, to_json_patch
old = {"name": "Alice", "age": 30}
new = {"name": "Bob", "age": 31, "active": True}
patch = to_json_patch(diff(old, new))
# [
# {"op": "replace", "path": "/age", "value": 31},
# {"op": "replace", "path": "/name", "value": "Bob"},
# {"op": "add", "path": "/active", "value": True},
# ]
# Round-trip: apply the patch to reconstruct `new` from `old`
from philiprehberger_json_diff import apply_json_patch
apply_json_patch(old, patch) == new # True
Array Diff Strategies
from philiprehberger_json_diff import diff, ArrayStrategy
old = {"tags": ["python", "json", "diff"]}
new = {"tags": ["diff", "json", "python"]}
# Default: order-sensitive (reports changes at each index)
changes = diff(old, new)
# Order-insensitive: treats arrays as unordered collections
changes = diff(old, new, array_strategy=ArrayStrategy.ORDER_INSENSITIVE)
# No changes reported — same elements, different order
Counting changes
from philiprehberger_json_diff import diff_count, has_changes
old = {"name": "Alice", "age": 30}
new = {"name": "Alice", "age": 31, "city": "NYC"}
diff_count(old, new) # 2 (one modify, one add)
has_changes(old, new) # True
has_changes(old, old) # False
# Both wrappers accept the same kwargs as diff(), including ignore patterns
has_changes(old, new, ignore={"age", "city"}) # False
HTML Output
from philiprehberger_json_diff import diff, format_html
changes = diff(old, new)
html = format_html(changes)
# Returns an HTML <table> with rows classed "added", "removed", or "modified"
API
| Function / Class | Description |
|---|---|
diff(a, b, ignore, mode, array_strategy) |
Compare two dicts/lists, returns list of Change objects or StructuralDiff |
diff_paths(a, b, paths, *, array_strategy) |
Compare two dicts/lists and return only changes matching the supplied glob-style path patterns |
format_diff(changes, color) |
Format changes as readable string with optional ANSI colors |
format_html(changes) |
Format changes as an HTML table for web UIs |
diff_summary(changes) |
Return dict with counts by change type |
diff_count(a, b, **kwargs) |
Return the number of changes between a and b (accepts the same kwargs as diff()) |
has_changes(a, b, **kwargs) |
Return True if a and b differ (accepts the same kwargs as diff()) |
to_json_patch(changes) |
Convert changes to RFC 6902 JSON Patch format |
apply_json_patch(target, ops) |
Apply RFC 6902 ops (add/remove/replace) to a target — inverse of to_json_patch |
apply_patch(target, changes) |
Apply a diff result as a patch to reconstruct the modified object |
Change |
Dataclass with path, change_type, old_value, new_value |
ChangeType |
Enum: ADDED, REMOVED, MODIFIED, UNCHANGED |
StructuralDiff |
Dataclass with key_additions, key_removals, value_changes, type_changes |
ArrayStrategy |
Enum: ORDER_SENSITIVE, ORDER_INSENSITIVE |
Development
pip install -e .
python -m pytest tests/ -v
Support
If you find this project useful:
License
Release files for philiprehberger-json-diff 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| philiprehberger_json_diff-0.6.0.tar.gz | 192.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| philiprehberger_json_diff-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 202.6 kB
Release files / philiprehberger_json_diff-0.6.0.tar.gz
| Download URL | philiprehberger_json_diff-0.6.0.tar.gz |
|---|---|
| Size | 192.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7f8484a6f980f6d08bff5bf56bfdb1dbcc71b14fe4624267c4344a21865a83e5
|
|
BLAKE2b-256 checksum How to use checksums |
776688235a24f46658bcb803f01475a4c0246667f2160df40cfb934329f0f8f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / philiprehberger_json_diff-0.6.0-py3-none-any.whl
| Download URL | philiprehberger_json_diff-0.6.0-py3-none-any.whl |
|---|---|
| Size | 10.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e3657224074873c3e5257cc0cc8a05952dbf0002440f40c959c5c4c92fa3b1ea
|
|
BLAKE2b-256 checksum How to use checksums |
885102edce5ffd4ef503405cbff086da236f88b889992549f72bd88233c09d3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|