DEV Community

tinali7564-eng
tinali7564-eng

Posted on Originally published at dailytoolbox.org

JSON in Production: 7 Subtle Bugs That Break Web APIs and How to Debug Them

JSON in Production: 7 Subtle Bugs That Break Web APIs and How to Debug Them

JSON (JavaScript Object Notation, RFC 8259) is the undisputed lingua franca of modern software engineering. Every REST API, GraphQL payload, configuration file, and serverless invocation relies on it.

Because JSON syntax looks remarkably simple—just braces, brackets, strings, numbers, and booleans—most developers assume serialization and deserialization are foolproof.

In production systems, this assumption leads to silent data corruption, crashing background jobs, and intermittent API failures.

In this engineering guide, we dissect the 7 most insidious JSON bugs encountered in high-scale production environments and demonstrate concrete patterns to prevent them.

1. The 64-Bit Integer Precision Disaster (Snowflake IDs)

The Problem

JavaScript numbers are double-precision 64-bit floats (IEEE 754). The maximum safe integer is:

`Number.MAX_SAFE_INTEGER === 9007199254740991; // (2^53 - 1)
`
Enter fullscreen mode Exit fullscreen mode

Modern distributed systems (such as Twitter/X, Discord, Stripe, and PostgreSQL BIGINT) use 64-bit integers for database primary keys and Snowflake IDs:

`18446744073709551615 (Unsigned 64-bit max)
`
Enter fullscreen mode Exit fullscreen mode

When your Go, Rust, or Java backend serializes a 64-bit ID as a raw JSON number:

`{
  "order_id": 18446744073709551615
}
`
Enter fullscreen mode Exit fullscreen mode

When parsed by a browser with JSON.parse():

`const data = JSON.parse('{"order_id": 18446744073709551615}');
console.log(data.order_id);
// Output: 18446744073709552000 (SILENTLY TRUNCATED!)
`
Enter fullscreen mode Exit fullscreen mode

Your client now sends requests for order_id: 18446744073709552000, resulting in phantom 404 errors or corrupting another customer's record!

The Fix

Always serialize 64-bit integers as strings across API boundaries:

`{
  "order_id": "18446744073709551615"
}
`
Enter fullscreen mode Exit fullscreen mode

2. Invisible Zero-Width Characters & Unicode Whitespace

The Problem

When users copy and paste text from rich-text editors, PDFs, Slack, or Word documents into forms, invisible Unicode characters often tag along:

  • Byte Order Mark ()

  • Zero-width space (​)

  • Non-breaking space ( )

Standard JSON parsers treat standard ASCII spaces (), tabs, and line breaks as whitespace, but strict JSON parsers reject invisible Unicode spaces inside keys or syntax delimiters:

`// Attempting to parse JSON with an invisible zero-width space before the key:
JSON.parse('{​"name": "Alex"}');
// SyntaxError: Unexpected token ​ in JSON at position 1
`
Enter fullscreen mode Exit fullscreen mode

The Fix

Sanitize incoming raw payload strings before parsing:

`function cleanJsonString(str: string): string {
  // Strip BOM and non-ASCII zero-width characters outside of string literals
  return str.replace(/^[​‌‍]+/, '');
}
`
Enter fullscreen mode Exit fullscreen mode

3. The Date Serialization Trap

The Problem

JSON has no native Date data type. When you pass a Date object to JSON.stringify(), it calls date.toISOString():

`const event = {
  title: "Sprint Planning",
  scheduledAt: new Date("2026-10-01T09:00:00Z")
};

const jsonStr = JSON.stringify(event);
// Result: '{"title":"Sprint Planning","scheduledAt":"2026-10-01T09:00:00.000Z"}'
`
Enter fullscreen mode Exit fullscreen mode

However, JSON.parse() does NOT reconstruct the Date object:

`const restored = JSON.parse(jsonStr);
console.log(typeof restored.scheduledAt); // "string", NOT Date!
restored.scheduledAt.getTime(); // TypeError: restored.scheduledAt.getTime is not a function
`
Enter fullscreen mode Exit fullscreen mode

The Fix

Use a reviver function when parsing date-heavy payloads:

`const ISO_DATE_REGEX = /^d{4}-d{2}-d{2}Td{2}:d{2}:d{2}(.d+)?Z$/;

function dateReviver(key: string, value: any) {
  if (typeof value === "string" && ISO_DATE_REGEX.test(value)) {
    return new Date(value);
  }
  return value;
}

const restored = JSON.parse(jsonStr, dateReviver);
console.log(restored.scheduledAt instanceof Date); // true
`
Enter fullscreen mode Exit fullscreen mode

4. Silent Dropping of undefined, Functions, and Symbols

The Problem

When serializing an object, JavaScript's JSON.stringify() silently omits keys whose values are undefined, functions, or Symbols:

`const user = {
  id: 101,
  nickname: undefined,
  getRole: () => "admin",
  specialFlag: Symbol("vip")
};

console.log(JSON.stringify(user));
// Output: '{"id":101}' (All other properties disappeared!)
`
Enter fullscreen mode Exit fullscreen mode

Furthermore, NaN and Infinity are silently coerced to null:

`JSON.stringify({ score: NaN, distance: Infinity });
// Output: '{"score":null,"distance":null}'
`
Enter fullscreen mode Exit fullscreen mode

The Fix

Always validate your payloads with TypeScript interfaces or schema validators (like Zod) to catch accidental undefined or NaN emissions before transmission.

5. Circular Reference Crashes

The Problem

In complex object graphs (such as DOM nodes, relational data models, or linked lists), objects frequently reference one another:

`const parent = { name: "Engineering" };
const child = { name: "Frontend", parent };
parent.child = child; // Circular reference!

JSON.stringify(parent);
// TypeError: Converting circular structure to JSON
`
Enter fullscreen mode Exit fullscreen mode

The Fix

Use a cycle-safe replacer with a WeakSet:

`function getCircularReplacer() {
  const seen = new WeakSet();
  return (key: string, value: any) => {
    if (typeof value === "object" && value !== null) {
      if (seen.has(value)) {
        return "[Circular Reference]";
      }
      seen.add(value);
    }
    return value;
  };
}

JSON.stringify(parent, getCircularReplacer());
`
Enter fullscreen mode Exit fullscreen mode

6. Trailing Commas & Unquoted Keys (JSON vs JSON5 / JSONC)

Many developers edit JSON configurations thinking they can leave trailing commas or add comments:

`{
  "name": "dailytoolbox",
  "version": "2.0.0", // Trailing comma below breaks standard JSON!
}
`
Enter fullscreen mode Exit fullscreen mode

RFC 8259 strictly forbids trailing commas, comments, and unquoted keys. Running standard JSON.parse() throws an instant SyntaxError.

7. Deeply Nested JSON Objects (Denial of Service)

Sending a payload with 10,000 nested brackets:

`{"a":{"a":{"a":{"a": ... }}}}
`
Enter fullscreen mode Exit fullscreen mode

Causes recursive stack overflow or high CPU lockup in unhardened server parsers.

Always configure request size limits (e.g. express.json({ limit: "100kb" })) and parser depth constraints on public API endpoints.

Bulletproof JSON Tools on DailyToolbox

Whenever you encounter cryptic JSON syntax errors or need to inspect nested API payloads:

  • Use our DailyToolbox Free JSON Formatter & Validator:
    🔍 Precise Error Highlighting: Pinpoints exact line and column numbers of syntax errors and trailing commas.

  • 🔒 100% Client-Side Privacy: Clean, format, and minify JSON without transmitting confidential business data over the network.

  • 🌳 Interactive Tree View: Expand, collapse, and search deep object graphs effortlessly.

Build resilient APIs by treating JSON serialization with the engineering rigor it deserves!


Originally published on DailyToolbox.org

Top comments (0)