deterministic-engine / safe_json.py
mirdax25-ux's picture
Release portable deterministic core 1.1.0: frozen formats, offline recovery and regression checks
ecad52f verified
Raw History Blame Contribute Delete
2.65 kB
"""Bounded UTF-8 JSON boundary, rejecting duplicate keys and non-finite values."""
import json
MAX_JSON_BYTES = 1_048_576
MAX_JSON_DEPTH = 32
def _pairs(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError("Duplicate JSON key")
result[key] = value
return result
def _constant(value):
raise ValueError("Non-finite JSON number")
def _float(value):
import math
parsed = float(value)
if not math.isfinite(parsed):
raise ValueError("Non-finite JSON number")
return parsed
def _integer(value):
# Cross-runtime bound, independent of Python's changing int conversion cap.
if len(value.lstrip("-")) > 128:
raise ValueError("JSON integer exceeds 128 digits")
return int(value)
def _check_text(text):
if not isinstance(text, str) or len(text) > MAX_JSON_BYTES:
raise ValueError("JSON input must be bounded UTF-8 text")
try:
if len(text.encode("utf-8", errors="strict")) > MAX_JSON_BYTES:
raise ValueError("JSON input too large")
except UnicodeEncodeError as exc:
raise ValueError("Invalid Unicode scalar value") from exc
# Limit nesting before asking a recursive JSON decoder to allocate objects.
depth = 0
quoted = escaped = False
for ch in text:
if quoted:
if escaped:
escaped = False
elif ch == "\\":
escaped = True
elif ch == '"':
quoted = False
elif ch == '"':
quoted = True
elif ch in "[{":
depth += 1
if depth > MAX_JSON_DEPTH:
raise ValueError("JSON nesting too deep")
elif ch in "]}":
depth -= 1
def strict_loads(text):
_check_text(text)
try:
value = json.loads(text, object_pairs_hook=_pairs, parse_constant=_constant, parse_float=_float, parse_int=_integer)
# Escaped surrogate pairs are decoded normally; lone surrogates fail.
stack = [value]
while stack:
item = stack.pop()
if isinstance(item, str):
item.encode("utf-8", errors="strict")
elif isinstance(item, dict):
stack.extend(item.keys())
stack.extend(item.values())
elif isinstance(item, list):
stack.extend(item)
return value
except (UnicodeError, RecursionError) as exc:
raise ValueError("Invalid JSON Unicode or nesting") from exc
def strict_dumps(value):
return json.dumps(value, ensure_ascii=True, sort_keys=True, indent=2, allow_nan=False)