Silent precision loss in prices, IDs and measurements is one of the most expensive JSON bugs.
JSON has a single number type with unlimited digits; the spec leaves precision to implementations. Python decodes numbers without a fraction or exponent as int (arbitrary size, exact) and the rest as float (64-bit binary, about 15 to 17 significant digits).
Consequences:
- Decimal fractions are approximated:
0.1 + 0.2is not0.3, and"19.90"decodes to19.9. For money, decode withparse_float=Decimaland encode Decimals as strings (or as numbers with a custom approach) so no binary rounding happens. - Python's large ints are exact, but JavaScript numbers are floats, so integers above
2**53(for example 64-bit database IDs or Twitter-style snowflakes) lose precision in browsers. APIs often send such IDs as strings for that reason. 1.0round-trips as1.0, not1, becauserepr(float)is preserved.
NaN and Infinity: Python writes float('nan') as NaN and infinity as Infinity. These are JavaScript literals, not valid JSON, and strict parsers reject them. Use allow_nan=False to raise ValueError instead, and convert such values to null yourself. On decoding, Python accepts them by default; parse_constant lets you reject them.
1e400 overflows to inf when parsed, so overflow quietly becomes Infinity too.
Example
import json
from decimal import Decimal
print(json.loads("[0.1, 19.90, 1.0]")) # [0.1, 19.9, 1.0]
prices = json.loads('{"a": 0.1, "b": 0.2}', parse_float=Decimal)
print(prices["a"] + prices["b"]) # 0.3
print(json.loads("[0.1]")[0] + 0.2) # 0.30000000000000004
big_id = 2**63 - 1
print(json.loads(json.dumps(big_id)) == big_id) # True in Python
print(float(big_id) == big_id + 1) # True: a JS float can't tell
print(json.dumps([float("nan"), float("inf")])) # [NaN, Infinity]
try:
json.dumps(float("nan"), allow_nan=False)
except ValueError as e:
print(e) # Out of range float values are not JSON compliant: nanRemember
- JSON numbers become exact ints or binary floats; use
parse_float=Decimalfor money - Integers above 2**53 lose precision in JavaScript clients; send big IDs as strings
allow_nan=Falsestops Python from emitting invalid NaN/Infinity
Watch out for
- Sending 64-bit IDs as JSON numbers to a browser and getting the last digits rounded.