File size: 1,470 Bytes
969891d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | """
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)
|