JSON feels deceptively simple. We use JSON.parse() and JSON.stringify() every day without a second thought. Because the syntax looks identical to JavaScript object literals, developers often assume JSON parsing behaves predictably across environments.
However, the JSON specification (RFC 8259) and language-specific implementations don't always align. Subtle edge cases can cause silent data corruption, lost precision, or unexpected production failures—often without throwing a single error.
Here are three JSON edge cases every developer should watch out for and how to handle them cleanly.
1. The 64-Bit Integer Precision Loss
Standard JavaScript numbers are IEEE 754 double-precision floats. The maximum safe integer in JavaScript is Number.MAX_SAFE_INTEGER (9,007,199,254,740,991 or 2^53 - 1).
Database primary keys, Twitter status IDs, and Snowflake IDs frequently exceed this limit. Consider what happens when your backend sends a 64-bit integer ID in a JSON payload:
{
"user_id": 112233445566778899
}
When you parse this with native JSON.parse() in JavaScript:
const payload = '{"user_id": 112233445566778899}';
const data = JSON.parse(payload);
console.log(data.user_id); // Output: 112233445566778900
Notice what happened? The parser silently rounded 112233445566778899 up to 112233445566778900. The operation succeeded with zero errors, but your frontend is now querying the database with an incorrect ID.
The Fix:
- Backends should serialize 64-bit integers as strings (
"user_id": "112233445566778899"). - On the client side, use custom parser libraries like
json-bigintwhen consuming third-party APIs that return unquoted 64-bit numbers.
2. Duplicate Key Collision Vulnerabilities
What happens when an incoming JSON object contains duplicate keys?
{
"role": "guest",
"role": "admin"
}
RFC 8259 Section 4 states that key names should be unique, but it does not strictly forbid duplicates. As a result, different JSON parsers handle this inconsistently:
-
V8 / Node.js / Browser JS: Overwrites previous values and retains
"admin". -
Python (
jsonmodule): Keeps the last key by default ("admin"). -
Go (
encoding/json): Overwrites and keeps the last key. -
Some API Gateways / WAFs: May parse only the first key (
"guest").
This inconsistency creates security risks. If your API gateway inspects the payload and sees "role": "guest", but passes the raw JSON to a backend service that evaluates the second key as "role": "admin", an attacker can bypass access controls.
The Fix:
Enforce strict schema validation (using tools like Zod or Ajv) to reject payloads with non-unique keys before processing them in business logic.
3. Unpaired Unicode Surrogates
JSON strings must contain valid UTF-8 characters. In JavaScript, strings are UTF-16 code units. If a string contains an unpaired surrogate (such as "?" without its accompanying low surrogate), JavaScript will happily stringify it:
const invalid = JSON.stringify({ text: "?" });
// Result: '{"text":"?"}'
While JS engines tolerate raw surrogate pairs, sending this string to a strict UTF-8 parser in Rust or Go will cause the request to fail with invalid encoding errors.
Debugging JSON Payloads Client-Side
When troubleshooting malformed JSON responses or checking nested API structures, pasting production payloads into server-side web tools can accidentally expose sensitive tokens or private user data.
Using a browser-based, client-side tool like the Nutilz JSON Formatter ensures your data is parsed locally in your browser memory without hitting external servers. It gives you instant syntax error highlighting and formatted tree views while keeping your payload private.
Best Practices for Defending Against JSON Glitches
- Treat 64-bit numbers as strings: Never pass raw 64-bit integers across JSON boundaries.
- Validate schemas early: Use a schema validator to reject malformed types and duplicate keys at the network edge.
-
Use reviver functions: Leverage the optional second argument of
JSON.parse(text, reviver)to sanitize types during parsing.
const safeData = JSON.parse(jsonString, (key, value) => {
if (typeof value === 'number' && value > Number.MAX_SAFE_INTEGER) {
return BigInt(value);
}
return value;
});
Conclusion
JSON is simple on the surface, but edge cases around number precision, duplicate keys, and Unicode encoding can lead to subtle production bugs. Understanding these engine behaviors helps you design more resilient APIs and client applications. For quick, private payload verification during development, you can use the client-side JSON Formatter on nutilz.com to validate and inspect your JSON without leaving your browser.
Top comments (1)
The
112233445566778899example is a particularly nasty failure becauseJSON.parse()succeeds while the identifier becomes112233445566778900; by then, a reviver cannot recover the original value. Duplicate keys create a different boundary problem: validating the already-parsed object with Zod or Ajv will not reveal that tworolefields were present, so strict duplicate detection has to happen during raw-token parsing or at the gateway. I'd treat JSON boundaries as contracts with explicit numeric and encoding rules, plus adversarial fixtures for parser differentials, rather than assuming every compliant-looking payload is.