118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
JSON Schema Validator CLI
|
|
A simple command-line tool to validate JSON files against JSON schemas.
|
|
Usage: python json_schema_cli.py <json_file> <schema_file> [--output report.json]
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import argparse
|
|
from typing import Any
|
|
|
|
|
|
def load_json(path: str) -> dict | list | None:
|
|
"""Load and parse JSON from file path."""
|
|
with open(path, 'r') as f:
|
|
return json.load(f)
|
|
|
|
|
|
def validate_data(data: Any, schema: dict) -> list[str]:
|
|
"""
|
|
Validate data against a JSON schema.
|
|
Returns list of validation errors (empty if valid).
|
|
"""
|
|
errors = []
|
|
|
|
# Basic schema structure check
|
|
if '$schema' not in schema and 'type' not in schema:
|
|
errors.append("Schema missing required fields ($schema or 'type')")
|
|
return errors
|
|
|
|
# Type validation
|
|
if 'type' in schema:
|
|
expected_type = schema['type']
|
|
actual_type = type(data).__name__
|
|
|
|
type_map = {
|
|
'string': str,
|
|
'number': (int, float),
|
|
'integer': int,
|
|
'boolean': bool,
|
|
'array': list,
|
|
'object': dict,
|
|
'null': type(None)
|
|
}
|
|
|
|
if expected_type in type_map:
|
|
expected_py = type_map[expected_type]
|
|
if not isinstance(data, expected_py):
|
|
errors.append(f"Type mismatch: expected '{expected_type}', got '{actual_type}'")
|
|
|
|
# Properties validation (for objects)
|
|
if isinstance(data, dict) and 'properties' in schema:
|
|
props = schema['properties']
|
|
for prop, prop_schema in props.items():
|
|
if prop in data:
|
|
prop_errors = validate_data(data[prop], prop_schema)
|
|
for err in prop_errors:
|
|
errors.append(f"Property '{prop}': {err}")
|
|
elif prop_schema.get('required', False):
|
|
errors.append(f"Missing required property: '{prop}'")
|
|
|
|
# Items validation (for arrays)
|
|
if isinstance(data, list) and 'items' in schema:
|
|
items_schema = schema['items']
|
|
for i, item in enumerate(data):
|
|
item_errors = validate_data(item, items_schema)
|
|
for err in item_errors:
|
|
errors.append(f"Item {i}: {err}")
|
|
|
|
# Required fields check
|
|
if isinstance(data, dict) and 'required' in schema:
|
|
for req_field in schema['required']:
|
|
if req_field not in data:
|
|
errors.append(f"Missing required field: '{req_field}'")
|
|
|
|
return errors
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Validate JSON against a schema')
|
|
parser.add_argument('json_file', help='Path to JSON file to validate')
|
|
parser.add_argument('schema_file', help='Path to JSON schema file')
|
|
parser.add_argument('--output', '-o', help='Output file for validation report')
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
data = load_json(args.json_file)
|
|
schema = load_json(args.schema_file)
|
|
except FileNotFoundError as e:
|
|
print(f"Error: File not found - {e}")
|
|
sys.exit(1)
|
|
except json.JSONDecodeError as e:
|
|
print(f"Error: Invalid JSON - {e}")
|
|
sys.exit(1)
|
|
|
|
errors = validate_data(data, schema)
|
|
|
|
report = {
|
|
'valid': len(errors) == 0,
|
|
'errors': errors,
|
|
'data_file': args.json_file,
|
|
'schema_file': args.schema_file
|
|
}
|
|
|
|
if args.output:
|
|
with open(args.output, 'w') as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"Report written to {args.output}")
|
|
|
|
print(json.dumps(report, indent=2))
|
|
|
|
sys.exit(0 if report['valid'] else 1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |