JSON Is Lying to You: Number Precision Loss and How to Detect It
JSON silently corrupts large integers and floating point numbers. Learn why it happens, how to detect it, and how ES2026's JSON.parse source access finally fixes it.
Have broken JSON right now? Fix it free in under 1 second — no signup.
Fix My JSON →Your JSON is valid. It passes every linter. And it's still returning the wrong number.
JSON has a silent data corruption problem: when numbers get large enough, or precise enough, they lose accuracy in transit. No error. No warning. The wrong value, silently substituted.
This post covers why it happens, which patterns are dangerous in production, and how to fix it — including ES2026's new JSON.parse() source access that finally gives JavaScript a clean escape hatch.
The Problem: IEEE 754 Doesn't Cover All JSON Numbers
JavaScript parses JSON numbers as IEEE 754 double-precision floats. This format gives you 53 bits of integer precision, which covers numbers up to 2^53 - 1 = 9007199254740991.
Any integer larger than that will be silently rounded to the nearest representable float:
JSON.parse('{"id": 9007199254740993}')
// → { id: 9007199254740992 } // Wrong! Lost the last digit
This is not a bug in JSON.parse. It's a correct implementation of a spec that has a fundamental mismatch with how other systems (databases, 64-bit IDs, financial systems) handle numbers.
The Four Ways JSON Loses Your Numbers
1. 64-bit Integer IDs (Databases, Snowflakes, Distributed Systems)
PostgreSQL bigint can hold values up to 9,223,372,036,854,775,807. Twitter Snowflake IDs are 64-bit. Stripe resource IDs, UUID-like numeric IDs, and auto-increment counters in large databases routinely exceed 2^53.
When these values go through a JSON API response and you parse them in JavaScript, they silently round:
// Stripe-style numeric ID from a large dataset
const payload = '{"invoice_id": 9007199254741234}'
const parsed = JSON.parse(payload)
console.log(parsed.invoice_id) // 9007199254741232 ← WRONG
The damage is invisible unless you compare the raw string value against the parsed value.
2. Floating-Point Accumulation in Financial Data
Even modest decimal numbers lose precision:
JSON.parse('{"price": 0.1}')
// → { price: 0.1 } // Looks fine
0.1 + 0.2
// → 0.30000000000000004 // Not fine
JSON.parse('{"total": 1234567.89}')
// → { total: 1234567.89 } // Fine here
JSON.parse('{"total": 12345678901234.56}')
// → { total: 12345678901234.56 } // Still fine
JSON.parse('{"total": 1234567890123456789.12}')
// → { total: 1234567890123456800 } // Silent rounding
For financial calculations, even small errors compound. The issue isn't just JSON — it's that floating-point arithmetic on decimal fractions is inherently imprecise, and JSON hands you back a float without warning.
3. Scientific Notation and Very Small Numbers
JSON.parse('{"value": 1e-400}')
// → { value: 0 } // Underflow to zero, silently
JSON.parse('{"value": 1e400}')
// → { value: Infinity } // Overflow, silently
APIs that use scientific notation for sensor readings, measurements, or statistical outputs can silently produce zeros or infinities in JavaScript consumers.
4. Integer Overflow in Summation
ES2025 already introduced Math.sumPrecise() to address floating-point summation errors:
// Old way — accumulates floating-point error
[0.1, 0.2, 0.3].reduce((a, b) => a + b, 0)
// → 0.6000000000000001
// ES2026 — precise summation
Math.sumPrecise([0.1, 0.2, 0.3])
// → 0.6
But this doesn't help if the numbers were already corrupted by JSON.parse before you get to sum them.
How to Detect Precision Loss in Your JSON
Before fixing anything, you need to know whether your specific payloads are affected. Two approaches:
Option 1: Compare String vs Parsed Value
function checkPrecisionLoss(jsonString) {
const parsed = JSON.parse(jsonString)
const reparsed = JSON.stringify(parsed)
// If any number changed representation, we lost precision
const original = JSON.parse(jsonString, (key, value) => {
if (typeof value === 'number') {
return String(value)
}
return value
})
// Compare raw numeric strings in original JSON vs after parse/stringify round-trip
const numberPattern = /:\s*(-?\d+\.?\d*(?:[eE][+-]?\d+)?)/g
const originalNumbers = [...jsonString.matchAll(numberPattern)].map(m => m[1])
const reparsedNumbers = [...reparsed.matchAll(numberPattern)].map(m => m[1])
return originalNumbers.some((n, i) => n !== reparsedNumbers[i])
}
Option 2: Use AI JSONMedic's Validator
The JSON Validator on AI JSONMedic will surface structural issues in your JSON. For number precision specifically, paste your payload and check whether the repaired output matches your expected values — particularly for large numeric IDs or financial amounts.
The Fix: Three Approaches
Fix 1: ES2026 JSON.parse Source Access (The Cleanest Solution)
ECMAScript 2026 adds a context parameter to the JSON.parse reviver that gives you access to the original source text of each value. This means you can access the exact string that was in the JSON before IEEE 754 conversion happens:
// ES2026 — access the raw source string for any parsed value
const result = JSON.parse('{"id": 9007199254740993, "price": 1234567890.99}', (key, value, context) => {
if (typeof value === 'number') {
// context.source is the original string from the JSON
const source = context.source
// Use BigInt for large integers
if (/^\d+$/.test(source) && BigInt(source) > BigInt(Number.MAX_SAFE_INTEGER)) {
return BigInt(source)
}
// Use string preservation for financial decimals
if (source.includes('.') && source.length > 15) {
return source // Return as string, process with Decimal.js
}
}
return value
})
console.log(result.id) // 9007199254740993n (BigInt, exact)
console.log(result.price) // 1234567890.99 (fine — within safe range)
The context.source property is the critical addition. Previously, by the time your reviver ran, the number had already been converted to a float. Now you can intercept it.
Browser support: available in V8 (Chrome/Node.js) from mid-2026, in SpiderMonkey (Firefox) from 2026, Safari TP. Check caniuse for production deployment timing.
Fix 2: BigInt for Known Large Integer Fields
If you control the schema and know which fields will be large integers, convert them explicitly:
function parseWithBigInt(json, bigIntFields) {
return JSON.parse(json, (key, value) => {
if (bigIntFields.includes(key) && typeof value === 'number') {
// Reconstruct from the stringified value — NOT perfect, but workable for known fields
return BigInt(Math.round(value))
}
return value
})
}
// Usage
const result = parseWithBigInt(
'{"id": 9007199254740993, "name": "test"}',
['id', 'user_id', 'invoice_id']
)
// result.id → 9007199254740993n (if the rounding didn't corrupt it first)
Warning: This approach has a window where rounding still happens (the JSON.parse pass converts to float first). It works for numbers that lose only a few ULPs (units in the last place), but for numbers that round significantly, you need the ES2026 source access approach.
Fix 3: String-Encode Numbers in Your API
The most portable fix: return large integers as strings in your JSON API, and document this in your schema:
{
"id": "9007199254740993",
"name": "test"
}
This is what Twitter did when their Snowflake IDs exceeded JavaScript's safe integer range. It's ugly but universally compatible across all JSON parsers regardless of language or runtime.
If you're generating a JSON schema for your API, the JSON Schema Generator can infer field types from a sample payload — useful for identifying which numeric fields might need string encoding.
Fix 4: Decimal.js or BigDecimal for Financial Math
For financial amounts where decimal precision matters:
import Decimal from 'decimal.js'
// Parse as string first, then convert to Decimal
const json = '{"amount": "1234567890.123456789"}' // API returns as string
const data = JSON.parse(json)
const amount = new Decimal(data.amount)
console.log(amount.plus('0.000000001').toString())
// → "1234567890.123456790" // Exact
Use string representation in your JSON schema for financial amounts, then convert at the application boundary.
When Does This Actually Bite You in Production?
The precision loss patterns that cause real incidents:
1. Database IDs → API → JavaScript clientPostgreSQL bigint auto-increment IDs on large tables. The ID rounds, you store the wrong value, and lookups fail silently or corrupt relationships.
A payment service accumulates float errors across thousands of microtransactions. The total is wrong. Reconciliation fails.
3. Timestamps as millisecondsUnix timestamps in milliseconds (e.g. 1754307840000) are currently safe — they're under 2^53. But timestamps with nanosecond precision (common in distributed tracing) are not.
When you ask an LLM to extract structured data that includes large numeric values, the model outputs the numbers as JSON. Your JSON.parse silently corrupts them. This is particularly common in data extraction pipelines where LLMs emit database IDs, financial figures, or timestamps from text.
For LLM-generated JSON that's already broken for other reasons (trailing commas, Python booleans, truncation), use AI JSONMedic to repair the structure first — then apply the precision fix in your parsing layer.
What JSON Parsers in Other Languages Do
JavaScript's behavior is not universal. This is worth knowing if you're building polyglot systems:
- Python:
json.loads()uses arbitrary precision for integers.12345678901234567890stays exact. Float precision still applies to decimals. - Go: Uses
float64by default (same precision loss), butjson.DecoderwithUseNumber()preserves numbers as strings until you convert them. - Java: Jackson's
BigDecimalNodeandBigIntegerNodehandle arbitrary precision if configured. - Rust (serde_json): Default behavior similar to JavaScript for large integers. The
arbitrary_precisionfeature flag preserves exact string representation.
If a Python microservice sends a large integer to a JavaScript frontend via JSON, the integer is exact leaving Python and corrupted arriving in the browser. The JSON spec itself doesn't define number precision — each parser implementation makes its own choice.
The Short Version
If your JSON contains:
- Integer IDs > 9007199254740991 (2^53 - 1)
- Financial decimals with >15 significant digits
- Scientific notation values near 0 or near ±Infinity
...you're at risk of silent precision loss in JavaScript.
Today's options: string-encode large numbers in your API, usejson-bigint library as a drop-in JSON.parse replacement, or use Go's UseNumber() decoder.
ES2026 (available now in Node.js 22+): JSON.parse(json, (k, v, ctx) => ...) with ctx.source gives you the raw string before conversion — use BigInt or Decimal.js from there.
The JSON Validator and JSON Repair tools on AI JSONMedic handle structural errors. For number precision, the fix is in your parsing layer — and ES2026 finally gives JavaScript the hook to do it right.
FAQ
Why doesn't JSON.parse throw an error when precision is lost?Because the number it produces is a valid IEEE 754 double — it's not an error from JavaScript's perspective. The spec doesn't require parsers to preserve precision beyond what the runtime's number type can represent.
Is this a JSON spec bug?The JSON spec (RFC 8259) says parsers should handle numbers with arbitrary precision, but implementations are not required to. The spec notes that "an implementation may set limits on the range and precision of numbers accepted." Most implementations set those limits at their native float type.
Does JSON5 fix this?No. JSON5 extends JSON syntax (allowing comments, trailing commas, single quotes) but doesn't change number handling. Precision loss still applies.
What's the best library for high-precision JSON in Node.js?json-bigint is the most common drop-in replacement. It parses integers as BigInt automatically. For decimal precision, pair it with Decimal.js for any arithmetic after parsing.
Does this affect JSON Schema validation?
JSON Schema validators like Ajv validate based on the parsed value — so if precision loss has already occurred, the validator sees the rounded value. It won't catch the corruption. This is another reason to apply precision fixes in the parsing layer before validation runs.
Still dealing with broken JSON?
Paste it in and get it fixed in under 1 second — free, no signup, no install. Works with ChatGPT, Claude, n8n, and any AI output.
Fix My JSON Free →Related Articles