Standard JSON has no way to write NaN or Infinity, so you have to pick a stand-in: null, a string such as "NaN" or "Infinity", or an error that stops bad data at the source. Whichever you choose, configure every serializer and parser in the pipeline the same way, because JavaScript, Python and Go each handle NaN and Infinity in JSON differently by default.
Why JSON has no NaN or Infinity
The JSON spec rules both values out. RFC 8259 says in its section on numbers: "Numeric values that cannot be represented in the grammar below (such as Infinity and NaN) are not permitted."
The grammar only allows digits, a sign, a decimal point and an exponent. There is no token for "not a number" or for infinity, so any document containing a bare NaN is not valid JSON, whatever produced it.
What each language does with NaN and Infinity in JSON
Each runtime picks a different default, so the problem usually shows up where two systems meet.
| Runtime or format | Writing NaN / Infinity | Reading NaN text |
|---|---|---|
JavaScript JSON.stringify |
Writes null |
JSON.parse throws a SyntaxError |
Python json (default) |
Writes NaN, Infinity, -Infinity |
Accepts them |
Python json, allow_nan=False |
Raises ValueError | (set by parse_constant) |
Go encoding/json |
Returns an UnsupportedValueError | Not valid JSON |
PostgreSQL json / jsonb |
(stores numbers as numeric) | NaN and infinity are disallowed |
| JSON5 | Allowed as literals | Allowed |
JavaScript turns them into null
JavaScript never fails on NaN; it quietly writes null. MDN's JSON.stringify page says "The numbers Infinity and NaN, as well as the value null, are all considered null." Its own example, JSON.stringify([NaN, null, Infinity]), returns '[null,null,null]'.
That's safe for the parser on the other end, but you lose information. A chart that receives null can't tell a failed calculation from a missing reading.
Python writes invalid JSON by default
Python's json module writes the bare literals unless you tell it not to. The Python json documentation says that with allow_nan set to True, the default, "their JavaScript equivalents (NaN, Infinity, -Infinity) are used." The docs admit this "is not JSON specification compliant, but is consistent with most JavaScript based encoders and decoders."
With allow_nan=False, the same call raises a ValueError instead, "in strict compliance with the JSON specification."
Go refuses to encode them
Go is the strictest of the three. The encoding/json docs say that when marshaling, "NaN and +/-Inf values will return an UnsupportedValueError." You find out at encode time, not in some other service later.
A real failure: Python to JavaScript
The classic bug is a Python backend that computes an average over zero rows and sends the result to a browser. Here is the server side:
import json
print(json.dumps({"avg": float("nan"), "max": float("inf")}))
# {"avg": NaN, "max": Infinity}
The output looks fine in a log, but it isn't JSON. In the browser, JSON.parse meets the bare NaN and fails. MDN's JSON.parse page explains why: "When JSON.parse receives a string that does not conform to the JSON grammar, it throws a SyntaxError." The page breaks, and the error points at the client even though the server wrote the bad data.
You can confirm it by pasting the response into a JSON validator, which reports the JSON.parse error. Then fix it on the Python side with one of the options below. If you see a different parse error, our guide to fixing invalid JSON covers the common ones.
Three ways to represent NaN and Infinity in JSON
The right choice depends on whether the consumer needs to know which special value it was.
- Use
null. Simple, valid everywhere, and what JavaScript already does. Pick it when "no number" is all the consumer needs to know. - Write strings. Write
"NaN","Infinity"and"-Infinity". The value survives the round trip, but every consumer has to know the convention and convert it back. - Raise an error. Use
allow_nan=Falsein Python, or let Go's UnsupportedValueError stop the request. Pick it when NaN means a bug upstream that you'd rather catch than ship.
My take: raise an error by default, and switch to null or strings only for fields where a missing or infinite value is expected, such as a ratio over an empty set. A NaN that travels three services as null is much harder to trace than one that fails where it was made.
Strings with a replacer and reviver in JavaScript
JavaScript can keep the special values by converting them to strings on the way out and back on the way in. A replacer function receives "the key and the value being stringified", per MDN, and a reviver transforms each parsed value before it is returned.
const replacer = (key, value) =>
typeof value === "number" && !Number.isFinite(value) ? String(value) : value;
const reviver = (key, value) =>
value === "NaN" || value === "Infinity" || value === "-Infinity"
? Number(value)
: value;
const text = JSON.stringify({ avg: NaN, max: Infinity }, replacer);
// {"avg":"NaN","max":"Infinity"}
const data = JSON.parse(text, reviver);
The limitation: the reviver converts every string that happens to equal "NaN", including a user's display name. Limit it to the keys you know hold numbers if your data contains free text.
Handle it in Python on both sides
Python gives you a switch for writing and a hook for reading. Set allow_nan=False in json.dumps to fail fast, or clean the values before dumping.
import json, math
def clean(x):
return None if isinstance(x, float) and not math.isfinite(x) else x
row = {"avg": float("nan"), "count": 0}
json.dumps({k: clean(v) for k, v in row.items()}, allow_nan=False)
# '{"avg": null, "count": 0}'
When reading JSON from a source that might send the bare literals, json.loads takes parse_constant, a function "called with one of the following strings: '-Infinity', 'Infinity', or 'NaN'." The docs suggest using it to raise an exception when invalid JSON numbers show up.
Databases and JSON5
PostgreSQL won't store these values in a JSON column either. Its JSON types documentation maps JSON numbers to the numeric type and notes "NaN and infinity values are disallowed."
JSON5 is the one format that accepts them: "Numbers may be IEEE 754 positive infinity, negative infinity, and NaN." It's a fine choice for config files you control, but a standard JSON parser on the other end still won't read it. See JSON vs JSON5 vs JSONC for the other differences, and why JSON.parse loses precision on large numbers for a related number problem.
FAQ
Is NaN valid JSON?
No. RFC 8259 says numeric values that cannot be represented in the JSON grammar, such as Infinity and NaN, are not permitted.
What does JSON.stringify do with NaN?
It writes null. MDN states that Infinity and NaN are considered null, so JSON.stringify([NaN, null, Infinity]) returns [null,null,null].
Why does Python output NaN in JSON?
Python's json.dumps has allow_nan=True by default, which writes the JavaScript literals NaN, Infinity and -Infinity. Pass allow_nan=False to get a ValueError instead.
Can JSON.parse read NaN?
No. JSON.parse throws a SyntaxError for any text that doesn't match the JSON grammar, and a bare NaN doesn't.
Which JSON format allows NaN and Infinity?
JSON5 does. Its spec allows IEEE 754 positive infinity, negative infinity and NaN, written as Infinity, -Infinity and NaN.