gotchapythonMajor
Python json.dumps emits Infinity/NaN that browser JSON.parse rejects, blanking a data page
Viewed 0 times
InfinityNaNallow_nanJSON.parseSyntaxError Unexpected token Iblank pageembedded json script tagparse_constant
Error Messages
Problem
A static HTML report embeds its data as a <script type="application/json"> block written by Python's json.dumps. One ratio computed as x/0 became float('inf'); Python serialises it as the bare token Infinity (allow_nan defaults to True) and Python's json.loads reads it back happily, so every server-side check passed. In the browser, JSON.parse throws "SyntaxError: Unexpected token 'I' ... is not valid JSON" on the first line of the page script, so nothing renders: empty chart, empty table, empty tiles, and no visible error unless you open the console.
Solution
1) Never let non-finite values reach the payload: map inf/NaN to None (JSON null) at the point where the ratio is computed, and render null as a dash in the page. 2) Write the embed with json.dumps(data, ensure_ascii=False, allow_nan=False) so a future non-finite value raises at generation time instead of producing an invalid file. 3) Add a regression test that runs the generator on a synthetic input containing the zero-denominator case and parses the output with json.loads(text, parse_constant=raise_) so the test fails on Infinity/NaN/-Infinity even though Python would accept them. 4) When a published page is blank, serve the local copy over a tiny http.server and read the browser console (or evaluate JSON.parse(document.getElementById('data').textContent) in the page) instead of re-validating the JSON in Python, which cannot reproduce the failure. Cache-bust the URL after regenerating, or the browser keeps showing the old file.
Why
JSON has no representation for infinities or NaN. Python's json module extends the format with the JavaScript-style tokens Infinity, -Infinity and NaN by default (allow_nan=True) and accepts them on read, so a Python-only round trip hides the problem; browsers implement strict JSON and reject the tokens.
Gotchas
- Python-side validation with json.loads passes; only a strict parser (parse_constant hook) or a real browser catches it.
- Any division where the denominator can be zero (rate vs a median baseline, growth vs a zero base) can produce inf; sparse categories in a larger list are the usual trigger after the list grows.
- A local http.server without a charset header shows mojibake for UTF-8 pages; that is unrelated to the JSON failure.
- Browsers may serve a regenerated local file from cache on plain navigation; add a query parameter to force a reload.
Code Snippets
Strict round-trip check for an embedded JSON payload
def _strict(text):
def bad(tok):
raise ValueError(f"non-finite token {tok!r} in JSON")
return json.loads(text, parse_constant=bad)
payload = json.dumps(data, ensure_ascii=False, allow_nan=False).replace("</", "<\\/")
_strict(payload) # fails on Infinity / NaN even though json.loads alone would accept themContext
Static analytics pages built by a Python script that embeds a JSON payload and renders it client-side; the page worked until the data grew to include rows with a zero baseline.
Revisions (0)
No revisions yet.