Formatter Validator Minifier Escape / Unescape JSON ↔ String JSON ↔ YAML JSON → CSV NDJSON / JSON Lines JSON → Types JSON ↔ XML JSON Diff JSON Patch Canonicalize (RFC 8785) Schema Validator Schema Generator JSONPath Tester JWT Decoder JSON Errors Schemas More Tools
JSONTools / Blog / Measured
Measured

Which languages silently corrupt large JSON numbers

The same JSON document, parsed and re-serialised by 11 parsers across 10 runtimes. No errors are raised anywhere below — the values simply come back different.

A 64-bit ID does not survive every parser

JSON's grammar permits arbitrary-precision numbers. Most parsers map them onto an IEEE 754 double, which represents integers exactly only up to 253−1. Above that the value is rounded, silently, with no error and no warning.

Of the 11 parsers measured, 4 lose precision on a 64-bit integer: JavaScriptCore JSON.parse, SpiderMonkey JSON.parse, V8 JSON.parse, encoding/json Unmarshal.

{"v":1541815603606036480} — a real Snowflake ID — comes back as 1541815603606036500 in every JavaScript engine and in Go. The last two digits are gone, and nothing tells you.

Four number probes across every parser

Parser263−1Snowflake ID30 decimal places1.0
CPython json.loads{"v": 9223372036854775807}{"v": 1541815603606036480}{"v": 3.141592653589793}{"v": 1.0}
Gson{"v":9223372036854775807}{"v":1541815603606036480}{"v":3.141592653589793238462643383{"v":1.0}
Jackson ObjectMapper{"v":9223372036854775807}{"v":1541815603606036480}{"v":3.141592653589793}{"v":1.0}
JavaScriptCore JSON.parse{"v":9223372036854776000}{"v":1541815603606036500}{"v":3.141592653589793}{"v":1}
Newtonsoft.Json{"v":9223372036854775807}{"v":1541815603606036480}{"v":3.141592653589793}{"v":1.0}
Ruby JSON.parse{"v":9223372036854775807}{"v":1541815603606036480}{"v":3.141592653589793}{"v":1.0}
SpiderMonkey JSON.parse{"v":9223372036854776000}{"v":1541815603606036500}{"v":3.141592653589793}{"v":1}
System.Text.Json{"v":9223372036854775807}{"v":1541815603606036480}{"v":3.141592653589793238462643383{"v":1.0}
V8 JSON.parse{"v":9223372036854776000}{"v":1541815603606036500}{"v":3.141592653589793}{"v":1}
encoding/json Unmarshal{"v":9223372036854776000}{"v":1541815603606036500}{"v":3.141592653589793}{"v":1}
json_decode{"v":9223372036854775807}{"v":1541815603606036480}{"v":3.141592653589793}{"v":1}

Green means the literal survived the round trip. Red means it changed.

One input, seven different outcomes

The probe {"v":1e400} — an exponent beyond the float64 range — is where agreement collapses completely:

ResultParsers
{"v":null}JavaScriptCore JSON.parse, SpiderMonkey JSON.parse, V8 JSON.parse
{"v":"Infinity"}Jackson ObjectMapper, Newtonsoft.Json
{"v":1e400}Gson, System.Text.Json
Falsejson_decode
{"v": Infinity}CPython json.loads
Infinity not allowed in JSONRuby JSON.parse
json: cannot unmarshal number 1e400 into Go vaencoding/json Unmarshal

Practical rules

Transport large identifiers as strings. If an ID can exceed 253−1, it is an identifier, not a quantity, and belongs in a string. This is why Twitter's API has always returned both id and id_str.

If you cannot change the producer, use a parser that preserves precision: json-bigint in JavaScript, parse_int= in Python's json.loads, json.Number with a Decoder in Go.

Test with a value above 9007199254740991. Anything smaller passes everywhere and tells you nothing.

Standards referenced

RFC 8259 §6 names 1E400 and 3.141592653589793238462643383279 as its own examples of numbers that signal interoperability problems — both are probes in this dataset.

Peer-reviewed literature

Only standards bodies and peer-reviewed venues are cited here.

How this was measured

Each probe was passed to every parser, the result re-serialised, and the output recorded verbatim. 165 observations in total. Re-serialising rather than inspecting in-memory values makes results comparable across languages that have different numeric types.

Every figure on this page came from executing the parser named, not from documentation. The collection harness is public — see the error registry method notes.

More on this