CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-jsondiff

Diff JSON and JSON-like structures in Python with multiple syntax support and bidirectional patching

Pending
Overview
Eval results
Files

diff-syntaxes.mddocs/

Diff Syntaxes

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.

Capabilities

Compact Syntax

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}}

Explicit Syntax

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'}}

Symmetric Syntax

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}]

RightOnly Syntax

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}

Custom Syntax Registration

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 Comparison

SyntaxUse CasePatch SupportUnpatch SupportOutput Size
compactGeneral purpose, storageMinimal
explicitHuman readable, debuggingMedium
symmetricBidirectional, version controlLarge
rightonlyFinal state focusMedium

Error Handling

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

docs

cli.md

core-operations.md

diff-syntaxes.md

index.md

jsondiff-class.md

serialization.md

tile.json