| """ |
| 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) |
|
|