""" JSON that is actually valid JSON. Python's `json.dumps` accepts NaN and Infinity by default and emits them as bare `NaN` / `Infinity` tokens. Those are not valid JSON: the Gemini API rejects the payload with `400 INVALID_ARGUMENT ... Unexpected token`, and a browser's `JSON.parse` throws on them too. Because `dumps` does not raise, the problem slips through any "try dumps, fall back on error" guard. Non-finite values arrive naturally from analytics — AVG over an empty group, a ratio with a zero denominator, ST_Area on a degenerate geometry. They are converted to null, which is what "no value" means in JSON. """ from __future__ import annotations import json import math from typing import Any def json_safe(obj: Any) -> Any: """Recursively replace non-finite floats with None so the result is valid JSON.""" if isinstance(obj, float): return None if (math.isnan(obj) or math.isinf(obj)) else obj if isinstance(obj, dict): return {k: json_safe(v) for k, v in obj.items()} if isinstance(obj, (list, tuple)): return [json_safe(v) for v in obj] return obj def dumps_safe(obj: Any) -> str: """ Serialize to strictly-valid JSON. `allow_nan=False` makes a missed non-finite value raise instead of producing a payload that fails later at the API or in the browser; `default=str` handles dates and other stragglers. """ return json.dumps(json_safe(obj), allow_nan=False, default=str)