Validating Orders in Python

Validating BookNest Orders in Python

This schema is the sample orders' contract, closed with additionalProperties: false at both levels:

order.schema.jsonJSON
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/booknest/order.schema.json",
  "type": "object", "additionalProperties": false,
  "required": ["order_id", "customer_id", "order_ts", "channel", "status", "currency",
               "items", "coupon", "discount", "total"],
  "properties": {
    "order_id": {"type": "integer", "minimum": 1},
    "customer_id": {"type": "integer", "minimum": 1},
    "order_ts": {"type": "string", "format": "date-time"},
    "channel": {"enum": ["ios", "android", "web"]},
    "status": {"enum": ["pending", "paid", "shipped", "delivered", "cancelled", "returned"]},
    "currency": {"const": "USD"},
    "items": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/item"}},
    "coupon": {"type": ["string", "null"], "pattern": "^[A-Z]+[0-9]{2}$"},
    "discount": {"$ref": "#/$defs/money"}, "total": {"$ref": "#/$defs/money"}
  },
  "$defs": {
    "money": {"type": "number", "minimum": 0},
    "item": {"type": "object", "additionalProperties": false,
             "required": ["book_id", "qty", "unit_price"],
             "properties": {"book_id": {"type": "integer", "minimum": 1, "maximum": 6},
                            "qty": {"type": "integer", "minimum": 1, "maximum": 3},
                            "unit_price": {"$ref": "#/$defs/money"}}}
  }
}

jsonschema 4.26.0 (github.com/python-jsonschema/jsonschema (https://github.com/python-jsonschema/jsonschema 4,988 ), pip 21,050 install "jsonschema[format]") implements drafts 3 to 2020-12; a FormatChecker makes format an assertion. On three corrupted orders in bad-orders.jsonl, the script reports every error with its location:

validate_orders.pyPython
import json, sys
from jsonschema import Draft202012Validator, FormatChecker
schema = json.load(open("order.schema.json", encoding="utf-8"))
v = Draft202012Validator(schema, format_checker=FormatChecker())
for n, line in enumerate(open(sys.argv[1], encoding="utf-8"), start=1):
    for err in v.iter_errors(json.loads(line)):
        print(f"line {n} {err.json_path}: {err.message}")
Output
line 1 $.items[0].qty: 0 is less than the minimum of 1
line 1 $.items[1].book_id: 7 is greater than the maximum of 6
line 2 $.order_ts: '2025-02-30T10:00:00Z' is not a 'date-time'
line 2 $.channel: 'fax' is not one of ['ios', 'android', 'web']
line 3 $: Additional properties are not allowed ('gift' was unexpected)
line 3 $: 'coupon' is a required property
line 3 $.total: '21.30' is not of type 'number'

All 100,000 sample orders pass, at about 1,000 to 1,500 a second on this shared 4-CPU host; fastjsonschema compiles schemas to Python code and ran 30 times faster, but supports only drafts 04 to 07.