"""Canonical JSON serialization: sorted keys, compact separators, no NaN/Infinity, deterministic float repr, UTF-8, ensure_ascii=False. NOT RFC 8785 (JCS), and not claimed to be. JCS is IEEE-754 double-based with its own number grammar; this module emits Python's `repr` for floats and passes int through unchanged. Three measured divergences from RFC 8785 Table 1: -0.0 -> "-0.0" (JCS: "0") 1e-06 -> "1e-06" (JCS: "0.000001") 2**53+1 -> "9007199254740993" (JCS max positive integer: 9007199254740992) Key order is `sort_keys=True`, i.e. by Unicode code point; JCS sorts by UTF-16 code unit, so the two diverge above U+FFFF. """ from __future__ import annotations import json import math from typing import Any class CanonicalError(ValueError): pass def _normalize(obj): if obj is None or isinstance(obj, bool) or isinstance(obj, int) or isinstance(obj, str): return obj if isinstance(obj, float): if not math.isfinite(obj): raise CanonicalError("non-finite float: %r" % obj) return obj if isinstance(obj, (list, tuple)): return [_normalize(x) for x in obj] if isinstance(obj, dict): out = {} for k, v in obj.items(): if not isinstance(k, str): raise CanonicalError("non-string key: %r" % (k,)) out[k] = _normalize(v) return out raise CanonicalError("unsupported type: %r" % (type(obj),)) def canonical(obj) -> str: n = _normalize(obj) return json.dumps(n, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False) def canonical_bytes(obj) -> bytes: return canonical(obj).encode("utf-8")