Diff JSON and JSON-like structures in Python with multiple syntax support and bidirectional patching
—
Multiple output formats for representing differences between JSON structures. Each syntax is optimized for different use cases, from compact storage to human readability to bidirectional operations. Syntaxes are accessed by name through the JsonDiffer class or diff() function parameters.
The default syntax focusing on minimizing output size while maintaining full diff information. Ideal for storage, transmission, and most programmatic use cases. Use syntax='compact' with diff() or JsonDiffer.
# Access via string name, not direct class import
syntax_name = 'compact' # Use with diff() or JsonDiffer(syntax='compact')Usage Examples:
from jsondiff import diff, JsonDiffer
from jsondiff.symbols import insert, delete, replace
# Dictionary changes
original = {'name': 'Alice', 'age': 30, 'skills': ['Python', 'Django']}
modified = {'name': 'Alice', 'age': 31, 'skills': ['Python', 'Django', 'Flask']}
result = diff(original, modified, syntax='compact')
# Result: {'age': 31, 'skills': {insert: [(2, 'Flask')]}}
# List with mixed operations
list1 = ['x', 'a', 'c', 'x']
list2 = ['a', 'b', 'c']
result = diff(list1, list2, syntax='compact')
# Result: {insert: [(1, 'b')], delete: [3, 0]}
# Complete replacement when similarity is low
dict1 = {'a': 1, 'b': 2, 'c': 3}
dict2 = {'x': 10, 'y': 20, 'z': 30}
result = diff(dict1, dict2, syntax='compact')
# Result: {replace: {'x': 10, 'y': 20, 'z': 30}}Clear, human-readable syntax that explicitly labels all operations. Best for debugging, logging, and when diff readability is important. Use syntax='explicit' with diff() or JsonDiffer.
# Access via string name, not direct class import
syntax_name = 'explicit' # Use with diff() or JsonDiffer(syntax='explicit')Usage Examples:
from jsondiff import diff
from jsondiff.symbols import insert, update, delete
# Dictionary with explicit operations
original = {'name': 'Alice', 'age': 30, 'temp': 'xyz'}
modified = {'name': 'Bob', 'age': 30, 'city': 'NYC'}
result = diff(original, modified, syntax='explicit')
# Result: {insert: {'city': 'NYC'}, update: {'name': 'Bob'}, delete: ['temp']}
# List operations clearly labeled
list1 = ['a', 'b', 'c']
list2 = ['a', 'x', 'c', 'd']
result = diff(list1, list2, syntax='explicit')
# Result: {insert: [(3, 'd')], update: {1: 'x'}}
# Set operations with clear labels
set1 = {'red', 'green', 'blue'}
set2 = {'red', 'yellow', 'purple'}
result = diff(set1, set2, syntax='explicit')
# Result: {add: {'yellow', 'purple'}, discard: {'green', 'blue'}}Maintains both original and modified values, enabling bidirectional operations. Supports both patch() and unpatch() operations for reversible diffs. Use syntax='symmetric' with JsonDiffer.
# Access via string name, not direct class import
syntax_name = 'symmetric' # Use with JsonDiffer(syntax='symmetric')Usage Examples:
from jsondiff import diff, JsonDiffer
from jsondiff.symbols import insert, delete
# Dictionary with value history
original = {'name': 'Alice', 'age': 30, 'status': 'active'}
modified = {'name': 'Alice', 'age': 31, 'city': 'NYC'}
result = diff(original, modified, syntax='symmetric')
# Result: {'age': [30, 31], insert: {'city': 'NYC'}, delete: {'status': 'active'}}
# Bidirectional operations
differ = JsonDiffer(syntax='symmetric')
# Forward patch: original -> modified
patched = differ.patch(original, result)
print(patched) # {'name': 'Alice', 'age': 31, 'city': 'NYC'}
# Reverse patch: modified -> original
unpatched = differ.unpatch(patched, result)
print(unpatched) # {'name': 'Alice', 'age': 30, 'status': 'active'}
# List with symmetric changes
list1 = ['a', 'b', 'c']
list2 = ['a', 'x', 'c', 'd']
result = diff(list1, list2, syntax='symmetric')
# Result: {1: ['b', 'x'], insert: [(3, 'd')]}
# Complete replacement with both values
dict1 = {'a': 1}
dict2 = {'b': 2}
result = diff(dict1, dict2, syntax='symmetric')
# Result: [{'a': 1}, {'b': 2}]Focuses exclusively on the modified (right) values, particularly for lists. Useful when only the final state matters, not the path to get there. Use syntax='rightonly' with diff() or JsonDiffer.
# Access via string name, not direct class import
syntax_name = 'rightonly' # Use with diff() or JsonDiffer(syntax='rightonly')Usage Examples:
from jsondiff import diff
# List shows final state only
original = ['Python', 'Django']
modified = ['Python', 'Django', 'Flask', 'React']
result = diff(original, modified, syntax='rightonly')
# Result: ['Python', 'Django', 'Flask', 'React']
# Dictionary still shows changes but focuses on final values
dict1 = {'name': 'Alice', 'skills': ['Python']}
dict2 = {'name': 'Alice', 'skills': ['Python', 'JavaScript', 'React']}
result = diff(dict1, dict2, syntax='rightonly')
# Result: {'skills': ['Python', 'JavaScript', 'React']}
# Useful for configuration updates where history doesn't matter
config1 = {'servers': ['srv1', 'srv2'], 'timeout': 30}
config2 = {'servers': ['srv1', 'srv3', 'srv4'], 'timeout': 60}
result = diff(config1, config2, syntax='rightonly')
# Result: {'servers': ['srv1', 'srv3', 'srv4'], 'timeout': 60}Built-in syntaxes are accessible by name through JsonDiffer and can be extended with custom implementations.
# Available built-in syntax names
available_syntaxes = ['compact', 'symmetric', 'explicit', 'rightonly']Usage Examples:
from jsondiff import JsonDiffer
# Use built-in syntax by name
differ = JsonDiffer(syntax='compact')
# Custom syntax class (advanced usage)
class VerboseJsonDiffSyntax:
def emit_value_diff(self, a, b, s):
if s == 1.0:
return {"status": "unchanged", "value": a}
else:
return {"status": "changed", "from": a, "to": b}
def emit_dict_diff(self, a, b, s, added, changed, removed):
# ... implement required method
pass
def emit_list_diff(self, a, b, s, inserted, changed, deleted):
# ... implement required method
pass
def emit_set_diff(self, a, b, s, added, removed):
# ... implement required method
pass
def patch(self, a, d):
# ... implement required method
pass
# Use custom syntax
custom_differ = JsonDiffer(syntax=VerboseJsonDiffSyntax())
result = custom_differ.diff({'x': 1}, {'x': 2})
# Result: {'x': {"status": "changed", "from": 1, "to": 2}}| Syntax | Use Case | Patch Support | Unpatch Support | Output Size |
|---|---|---|---|---|
compact | General purpose, storage | ✓ | ✗ | Minimal |
explicit | Human readable, debugging | ✗ | ✗ | Medium |
symmetric | Bidirectional, version control | ✓ | ✓ | Large |
rightonly | Final state focus | ✓ | ✗ | Medium |
Syntax classes may raise exceptions for malformed diffs:
from jsondiff import JsonDiffer
differ = JsonDiffer(syntax='symmetric')
# Invalid symmetric diff structure
try:
result = differ.patch({'a': 1}, "invalid_diff")
except Exception as e:
print(f"Invalid diff format: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-jsondiff