Diff JSON and JSON-like structures in Python with multiple syntax support and bidirectional patching
—
File-based diff operations with support for multiple input formats and syntax options. The jdiff command provides a convenient way to compare JSON and YAML files from the command line with all the power of the jsondiff library.
The jdiff command is installed automatically with the jsondiff package and provides file-based diff operations.
jdiff [-h] [-p] [-s {compact,symmetric,explicit,rightonly}] [-i INDENT] [-f {json,yaml}] first second
# Positional arguments:
# first Path to first file to compare
# second Path to second file to compare (or diff file when using --patch)
# Optional arguments:
# -h, --help Show help message and exit
# -p, --patch Apply patch mode: treat second file as diff to apply to first
# -s, --syntax Diff syntax for output formatting (default: compact)
# -i, --indent Number of spaces for indentation, None for compact (default: None)
# -f, --format File format for input and output (default: json)Compare two files and output the difference using various syntax options.
Usage Examples:
# Basic JSON file comparison
jdiff config1.json config2.json
# With pretty-printed output
jdiff config1.json config2.json -i 2
# Using explicit syntax for readability
jdiff data1.json data2.json -s explicit -i 2
# Compact output (default)
jdiff file1.json file2.json -s compact
# YAML file comparison
jdiff config1.yaml config2.yaml -f yaml
# Mixed format with YAML output formatting
jdiff data1.json data2.json -f yaml -i 2Sample Input and Output:
# config1.json
{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"debug": false
}
# config2.json
{
"database": {
"host": "production.db.com",
"port": 5432,
"name": "myapp_prod"
},
"debug": true,
"cache": {
"enabled": true
}
}
# Command: jdiff config1.json config2.json -s explicit -i 2
# Output:
{
"$insert": {
"cache": {
"enabled": true
}
},
"$update": {
"database": {
"host": "production.db.com",
"name": "myapp_prod"
},
"debug": true
}
}Apply a previously computed diff to a file to produce the modified version.
Usage Examples:
# Create a diff and save it
jdiff original.json modified.json > changes.json
# Apply the diff to the original file
jdiff original.json changes.json --patch
# Apply patch with pretty formatting
jdiff original.json changes.json --patch -i 2
# YAML patch operations
jdiff config.yaml patch.yaml --patch -f yaml
# Chain operations: create diff, then apply it
jdiff file1.json file2.json > diff.json
jdiff file1.json diff.json --patch > result.jsonPatch Workflow Example:
# Step 1: Create original file (original.json)
{
"version": "1.0",
"features": ["auth", "logging"]
}
# Step 2: Create target file (target.json)
{
"version": "1.1",
"features": ["auth", "logging", "caching"],
"experimental": true
}
# Step 3: Generate diff
jdiff original.json target.json -i 2 > upgrade.json
# upgrade.json contains:
{
"version": "1.1",
"features": {
"$insert": [
[2, "caching"]
]
},
"experimental": true
}
# Step 4: Apply diff to original
jdiff original.json upgrade.json --patch -i 2
# Output matches target.json:
{
"version": "1.1",
"features": [
"auth",
"logging",
"caching"
],
"experimental": true
}Handle both JSON and YAML files with automatic format detection and conversion.
Usage Examples:
# JSON to JSON (default)
jdiff data1.json data2.json
# YAML to YAML
jdiff config1.yaml config2.yaml -f yaml
# JSON input with YAML-formatted output
jdiff data1.json data2.json -f yaml -i 2
# YAML input with JSON-formatted output
jdiff config1.yaml config2.yaml -f json -i 2Format Conversion Example:
# config1.yaml
database:
host: localhost
port: 5432
debug: false
# config2.yaml
database:
host: remote-db
port: 5432
debug: true
cache:
ttl: 300
# Command: jdiff config1.yaml config2.yaml -f json -s explicit -i 2
# Output (JSON format):
{
"$insert": {
"cache": {
"ttl": 300
}
},
"$update": {
"database": {
"host": "remote-db"
},
"debug": true
}
}
# Command: jdiff config1.yaml config2.yaml -f yaml -s explicit
# Output (YAML format):
$insert:
cache:
ttl: 300
$update:
database:
host: remote-db
debug: trueChoose from different diff representation formats based on your needs.
Usage Examples:
# Compact syntax (minimal output, default)
jdiff file1.json file2.json -s compact
# Explicit syntax (clear operation labels)
jdiff file1.json file2.json -s explicit
# Symmetric syntax (bidirectional, includes original values)
jdiff file1.json file2.json -s symmetric
# Right-only syntax (focus on final values)
jdiff file1.json file2.json -s rightonlySyntax Comparison Example:
# Input files:
# file1.json: {"a": 1, "b": 2, "c": 3}
# file2.json: {"a": 1, "b": 5, "d": 4}
# Compact syntax:
jdiff file1.json file2.json -s compact
# Output: {"b": 5, "d": 4, "$delete": ["c"]}
# Explicit syntax:
jdiff file1.json file2.json -s explicit
# Output: {"$insert": {"d": 4}, "$update": {"b": 5}, "$delete": ["c"]}
# Symmetric syntax:
jdiff file1.json file2.json -s symmetric
# Output: {"b": [2, 5], "$insert": {"d": 4}, "$delete": {"c": 3}}
# Right-only syntax:
jdiff file1.json file2.json -s rightonly
# Output: {"b": 5, "d": 4, "$delete": ["c"]}The command-line interface is implemented in the jsondiff.cli module and provides structured argument parsing and error handling.
def main():
"""Command-line interface entry point."""
def load_file(serializer, file_path):
"""
Load and parse file using specified serializer.
Parameters:
- serializer: Serializer instance for the file format
- file_path: str, path to file to load
Returns:
dict or None: Parsed data or None if error occurred (error messages printed to stdout)
"""The CLI provides clear error messages for common issues:
# File not found
$ jdiff nonexistent.json file2.json
nonexistent.json does not exist
# Invalid JSON/YAML
$ jdiff invalid.json file2.json
invalid.json is not valid json
# Invalid format specification
$ jdiff file1.json file2.json -f xml
jdiff: error: argument -f/--format: invalid choice: 'xml' (choose from 'json', 'yaml')
# Invalid syntax specification
$ jdiff file1.json file2.json -s invalid
jdiff: error: argument -s/--syntax: invalid choice: 'invalid' (choose from 'compact', 'symmetric', 'explicit', 'rightonly')
# Help information
$ jdiff --help
usage: jdiff [-h] [-p] [-s {compact,symmetric,explicit,rightonly}] [-i INDENT] [-f {json,yaml}] first second
...The CLI tool integrates well with shell scripts and automation workflows:
#!/bin/bash
# Configuration diff checker
check_config_changes() {
local original="$1"
local modified="$2"
# Check if files exist
if [[ ! -f "$original" ]] || [[ ! -f "$modified" ]]; then
echo "Error: Files not found"
return 1
fi
# Generate diff
local diff_output=$(jdiff "$original" "$modified" -s explicit 2>/dev/null)
# Check if there are changes
if [[ "$diff_output" == "{}" ]]; then
echo "No configuration changes detected"
return 0
else
echo "Configuration changes detected:"
echo "$diff_output" | jq '.' 2>/dev/null || echo "$diff_output"
return 1
fi
}
# Usage
check_config_changes "prod-config.json" "staging-config.json"
# Automated deployment diff
generate_deployment_diff() {
local current_config="$1"
local target_config="$2"
local diff_file="deployment-changes.json"
jdiff "$current_config" "$target_config" -s compact > "$diff_file"
if [[ -s "$diff_file" ]]; then
echo "Deployment changes saved to $diff_file"
# Apply changes
jdiff "$current_config" "$diff_file" --patch > "updated-config.json"
echo "Updated configuration saved to updated-config.json"
else
echo "No deployment changes needed"
rm "$diff_file"
fi
}The CLI tool is optimized for file-based operations:
-f parameter, not file extensionInstall with Tessl CLI
npx tessl i tessl/pypi-jsondiff