Python Accepts NaN JSON but the Consumer Rejects It: Check Nonfinite Values
By ON Lab · Sources checked November 8, 2026
Python’s default JSON encoder can emit unquoted NaN and Infinity values that a strict JSON consumer rejects. Use allow_nan=False when the outgoing contract requires rejecting those float values. Separately, Python’s decoder option strict=True does not reject those named constants; it controls another decoding rule.
Reproduce the producer and consumer separately
The historical Requests report behind this guide used version 2.25.1 and was later closed as fixed. We did not install or test Requests and do not claim that old client behavior persists. Our current fixture uses Python’s standard library and a local Node JSON consumer, with no HTTP requests.
import json
payload = {"x": float("nan")}
wire = json.dumps(payload)
print(wire)
print(json.loads(wire, strict=True))Python 3.13.18 emitted {"x": NaN} and accepted it again. A Python-only round trip therefore hid the interoperability problem. Node 24.22.2 JSON.parse rejected that same text with SyntaxError. Infinity and -Infinity produced the same accept/reject distinction in our three special-value cases.
Fail before sending nonfinite values
import json
payload = {"x": float("nan")}
try:
wire = json.dumps(payload, allow_nan=False)
except ValueError as error:
print(type(error).__name__)
# Stop here and resolve the domain's missing/nonfinite-value policy.This raised ValueError locally. A finite control value, 1.25, serialized normally. Keep that rejection visible instead of catching it and then sending a manually assembled body. The encoder flag does not decide whether a missing observation should become null, be omitted, or be rejected by the application.
Replacing every NaN with 0 changes meaning. A zero measurement and an unknown measurement are not interchangeable. If a schema permits null, convert according to that documented schema before encoding; do not use a universal numeric replacement solely to make serialization pass.
Reject named constants on input explicitly
import json
def reject_constant(token):
raise ValueError("Nonfinite JSON constant: " + token)
try:
json.loads('{"x": NaN}', parse_constant=reject_constant)
except ValueError as error:
print(error)Our callback rejected NaN, Infinity and -Infinity. In contrast, strict=True alone accepted them. That option’s name is easy to overread: it concerns control characters in strings rather than promising every possible strictness rule or validating an application schema.
A large numeric token is another boundary
We also tested {"x": 1e400}. It is not one of the named constants, so parse_constant was not called. Python’s default float conversion produced infinity, and Node’s parser also accepted the numeric token but produced a nonfinite Number. Rejecting named constants alone is therefore not a complete finite-number policy.
If your domain requires finite values within a range, validate the parsed result and its schema. Decimal parsing may help when preserving a decimal representation is required, but it introduces its own type and serialization contract. The separate Decimal construction guide addresses input provenance, not full JSON validation.
Do not confuse format, transport and application success
| Check | What it establishes |
|---|---|
| Outgoing allow_nan=False | Those nonfinite Python float values are rejected during encoding. |
| A consumer parses the body | That consumer accepted its text syntax, not necessarily the required numeric domain. |
| The schema accepts the object | Application type/range rules were checked according to that schema. |
| An HTTP operation completes | A separate transport/application outcome that these local tests did not exercise. |
Likewise, one valid JSON value is different from multiple concatenated values. The JSON framing guide covers that separate failure. Do not change delimiters to solve a nonfinite-number token.
The official standard library and original reports are free alternatives. This guide adds a real second-language consumer and an overflow counterexample so that a successful local parse is not mistaken for a complete contract check.
Method: Python 3.13 documentation, a historical fixed Requests report, five decoder inputs and four encoder values tested with Python 3.13.18 and Node 24.22.2. No live request, current Requests behavior, production data or complete RFC/schema validator was tested.