Files
troupe-standup/wizard-work/spark/json_diff.py
T

72 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""json_diff.py - Compare two JSON files for structural equality.
Usage: python3 json_diff.py <file1.json> <file2.json>
Returns exit code 0 if JSON structures match (ignoring key order in objects),
1 if they differ, 2 on error. Outputs the diff to stdout.
"""
import json
import sys
from pathlib import Path
def normalize_json(data):
"""Recursively normalize JSON: sort dict keys, convert lists."""
if isinstance(data, dict):
return {k: normalize_json(data[k]) for k in sorted(data.keys())}
elif isinstance(data, list):
return [normalize_json(item) for item in data]
else:
return data
def json_equal(file1: str, file2: str) -> tuple:
"""Compare two JSON files. Returns (equal: bool, diff: str, error: str|None)."""
try:
data1 = json.loads(Path(file1).read_text())
data2 = json.loads(Path(file2).read_text())
norm1 = normalize_json(data1)
norm2 = normalize_json(data2)
if norm1 == norm2:
return True, "", None
diff_lines = []
diff_lines.append(f"Files differ:")
diff_lines.append(f" {file1}: {json.dumps(norm1, indent=2)[:500]}")
diff_lines.append(f" {file2}: {json.dumps(norm2, indent=2)[:500]}")
return False, "\n".join(diff_lines), None
except FileNotFoundError as e:
return False, "", f"File not found: {e.filename}"
except json.JSONDecodeError as e:
return False, "", f"JSON error in {e.doc}: line {e.lineno}"
except Exception as e:
return False, "", str(e)
def main():
if len(sys.argv) != 3:
print("Usage: python3 json_diff.py <file1.json> <file2.json>", file=sys.stderr)
sys.exit(2)
result, diff, error = json_equal(sys.argv[1], sys.argv[2])
if error:
print(f"Error: {error}", file=sys.stderr)
sys.exit(2)
if result:
print("STRUCTURALLY EQUAL")
sys.exit(0)
else:
print(diff)
sys.exit(1)
if __name__ == "__main__":
main()