Finite JSON Numbers: Reject constants, Check finite floats, Encode before sending

Reject NaN and Infinity in Python JSON Before Sending API Data

Stop Python JSON exports from silently emitting NaN or Infinity. Validate incoming constants, catch overflowed floats and test an API payload safely.

Python can produce a JSON-looking payload containing NaN, Infinity or -Infinity without raising an error by default. Those tokens are not valid JSON numbers. If an API rejects a report that your script successfully encoded, make the encoder reject non-finite floating-point values before any network request. For incoming data, use a separate decoding check: the output setting does not configure the input parser.

This tutorial uses the standard library and an in-memory example, not a real API or production dataset. It is useful for monitoring exports, sensor readings and calculated reports where missing or overflowing values can enter an otherwise ordinary dictionary.

Understand the default before changing your data

The official Python JSON documentation explicitly describes acceptance and emission of these non-standard numeric constants. A float representing an undefined calculation is not the same as an absent measurement, and neither automatically means zero. Decide what the receiving schema expects rather than replacing every troublesome value with a convenient number.

import json

payload = {"temperature": float("nan")}
encoded = json.dumps(payload, allow_nan=False)

This example is intended to raise ValueError. That is the desired boundary: the invalid measurement cannot silently become an outbound JSON number. Do not send encoded data from an earlier successful run after this failure. Keep the failed record separate and report which measurement needs investigation.

Encode the entire payload before sending anything

Use json.dumps with allow_nan=False to finish serialization in memory before handing the text to a transport. The same option is available on json.dump, but writing directly into a destination file can leave partial output if a later value fails. Validating the full string first avoids that particular partial-write problem; it is not a substitute for an atomic file replacement strategy.

import json

def encode_payload(payload):
    return json.dumps(payload, allow_nan=False)

for value in (12.5, float("nan"), float("inf"), float("-inf")):
    try:
        text = encode_payload({"reading": value})
    except ValueError:
        print("Rejected non-finite reading")
    else:
        print(text)

The finite case should encode normally; the other three cases should reach the rejection branch. These are expected outcomes for the example, not evidence of testing a particular API. If missing readings are permitted, explicitly map an identified missing value to None so that it becomes null, and record why. An API may reject null as well, so check its field contract first.

Reject incoming non-standard constants separately

On decoding, parse_constant receives the literal strings NaN, Infinity and -Infinity. A callback that raises ValueError prevents those constants from entering your data structure. Setting strict=True is not the solution to this problem: the decoder’s strict setting concerns control characters inside strings, not a blanket guarantee of standards compliance.

import json

def reject_constant(token):
    raise ValueError(f"Non-standard JSON number: {token}")

def decode_payload(text):
    return json.loads(text, parse_constant=reject_constant)

good = decode_payload('{"reading": 12.5}')
# This intentionally raises ValueError:
# decode_payload('{"reading": NaN}')

A quoted string such as "NaN" is still a string, not a numeric constant. If the schema requires a number, validate the decoded field type too. JSON parsing does not establish that every field has the application’s required meaning.

Check exponent overflow and other validation gaps

A token such as 1e400 uses ordinary JSON number syntax, so parse_constant does not handle it. Under the default float conversion it can become infinity. For a float-based workflow, check the resulting values with math.isfinite before using them, or run the strict encoder over the decoded object as a further non-finite-value check. Applications requiring large exact decimal values should evaluate decimal.Decimal through parse_float and design a compatible encoding strategy rather than silently losing range or precision.

These numeric checks also do not reject repeated object keys. For configuration input, pair them with the separate approach in rejecting duplicate JSON keys before loading configuration. Keep schema validation, payload-size limits and permissions as separate controls.

Quick checklist and common questions

  • Does the outgoing path use allow_nan=False?
  • Does the incoming path reject non-standard constants?
  • Are decoded float values finite and within the application’s acceptable range?
  • Are missing readings handled according to the receiving schema rather than converted to zero?
  • Does an encoding failure stop the send operation?

Will this make every API accept the payload? No. Required fields, types, authentication and business rules still apply. Should I catch every exception and continue? No. Catch the expected validation errors at a deliberate boundary and preserve enough non-sensitive context to fix the source record.

Source: Python JSON documentation, particularly encoder options, parse_constant and the non-finite-number compatibility section.

Safety: This tutorial is for educational purposes. Test carefully before using it in production. The site owner is not responsible for misuse, damage, data loss or production issues.

Leave a Reply

Your email address will not be published. Required fields are marked *