DEV Community

Cover image for 10 JSON Errors Every Developer Hits (And Exactly How to Fix Them)
99Tools
99Tools

Posted on

10 JSON Errors Every Developer Hits (And Exactly How to Fix Them)

JSON looks simple — just key-value pairs, right? But its strictness is legendary. No trailing commas. No comments. No single quotes. No undefined. Miss one rule and your entire payload is dead on arrival.

Even senior developers get caught by these. Not because they don't know JSON — because the spec is unforgiving in ways that feel arbitrary until you understand why those rules exist.

This article covers the 10 most common JSON errors, why they happen, and exactly how to fix them — with real-world code examples, not toy snippets.


Quick Reference Table

# Error The Fix
1 Trailing commas Remove the last comma in arrays/objects
2 Single quotes Use double quotes " for all keys and values
3 undefined / NaN / Infinity Replace with null or a valid number
4 Unescaped special characters Escape ", \, and control chars in strings
5 Mismatched brackets Lint your JSON before sending
6 Comments in JSON Strip comments; use JSON5 if you need them
7 Wrong data types Validate types at the boundary — never silently coerce
8 Circular references Use a replacer function or the flatted library
9 Date objects Store as ISO strings; parse explicitly on the way out
10 Large integers Use strings or BigInt for numbers > 2⁵³ − 1

Why Does JSON Keep Tripping Developers Up?

JSON was designed as a minimal, language-agnostic data format. That minimalism is intentional — but it means there's almost zero forgiveness baked in.

Unlike JavaScript (which has semicolon insertion, loose equality, and other guardrails), JSON has zero tolerance for syntax deviations.

The other issue: most JSON errors surface at runtime — often in production, often deep inside a third-party API response you didn't write. Editor linting helps, but it can't catch errors in dynamically generated payloads.

That's what this guide is for.


Error #1: Trailing Commas That Kill Your Parser

What's happening

A trailing comma is a comma after the last item in an array or object. JavaScript has allowed this since ES5. JSON absolutely does not.

The error you'll see

SyntaxError: Unexpected token } in JSON at position 42
Enter fullscreen mode Exit fullscreen mode

❌ Wrong

{
  "user": {
    "name": "Priya Kapoor",
    "role": "admin",
    "permissions": ["read", "write", "delete",]
  },
}
Enter fullscreen mode Exit fullscreen mode

Two trailing commas here: after "delete" and after the closing } of user.

✅ Fixed

{
  "user": {
    "name": "Priya Kapoor",
    "role": "admin",
    "permissions": ["read", "write", "delete"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Remove the last comma from every array and object. Most JSON linters flag this instantly.

💡 Pro tip: Never hand-edit minified JSON. Always pretty-print first (JSON.stringify(obj, null, 2)), edit, then re-minify. The structure is visible and trailing commas are easy to catch.


Error #2: Single Quotes Instead of Double Quotes

What's happening

JavaScript lets you use single quotes, double quotes, or backticks for strings. JSON only allows double quotes — for both keys and string values.

The error you'll see

SyntaxError: Unexpected token ' in JSON at position 0
Enter fullscreen mode Exit fullscreen mode

❌ Wrong

{
  'endpoint': '/api/v2/orders',
  'timeout': '30s'
}
Enter fullscreen mode Exit fullscreen mode

✅ Fixed

{
  "endpoint": "/api/v2/orders",
  "timeout": "30s"
}
Enter fullscreen mode Exit fullscreen mode

The sneaky variant

This bites developers who copy a JavaScript object literal and paste it into a JSON file:

// Valid JS object literal
const config = {
  host: 'localhost',
  port: 5432
}
Enter fullscreen mode Exit fullscreen mode

That object has unquoted keys — which is also invalid JSON, even if you swap in double quotes. Both the key format and the string delimiters need to change.

💡 Pro tip: If you accept JSON from users or external systems, always wrap JSON.parse() in a try/catch. Never assume the format is correct.


Error #3: undefined, NaN, and Infinity Aren't Valid JSON

What's happening

JavaScript has special values — undefined, NaN, Infinity, -Infinity — that simply don't exist in the JSON spec. When you serialize an object containing these, JSON.stringify() silently drops or converts them.

The error you'll see

No error. That's the problem. The values just vanish silently.

const payload = {
  userId: 42,
  score: NaN,
  bonus: Infinity,
  metadata: undefined
};

console.log(JSON.stringify(payload));
// {"userId":42,"score":null,"bonus":null}
// ⚠️ metadata key is gone entirely
Enter fullscreen mode Exit fullscreen mode

❌ Wrong

const stats = {
  avgResponseTime: totalTime / requestCount, // 💥 NaN if requestCount === 0
  maxRetries: Infinity
};

fetch('/api/stats', {
  method: 'POST',
  body: JSON.stringify(stats)
});
// Server receives: {"avgResponseTime":null,"maxRetries":null}
Enter fullscreen mode Exit fullscreen mode

✅ Fixed

function sanitizeForJSON(value) {
  if (typeof value !== 'number') return value;
  if (Number.isNaN(value) || !Number.isFinite(value)) return null;
  return value;
}

const stats = {
  avgResponseTime: sanitizeForJSON(totalTime / requestCount) ?? 0,
  maxRetries: 100 // Use a real cap, not Infinity
};
Enter fullscreen mode Exit fullscreen mode

Or use a replacer with JSON.stringify:

const safeStringify = (obj) =>
  JSON.stringify(obj, (key, value) => {
    if (typeof value === 'number' && !Number.isFinite(value)) return null;
    if (value === undefined) return null;
    return value;
  });
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: JSON.stringify also silently drops functions and Symbol values. If your object might contain these, validate the output or use a schema validator like zod or ajv before sending.


Error #4: Unescaped Special Characters in Strings

What's happening

Certain characters inside JSON strings must be escaped with a backslash. The most common culprits: double quotes, backslashes, and control characters like newlines and tabs.

The error you'll see

SyntaxError: Bad escaped character in JSON at position 34
Enter fullscreen mode Exit fullscreen mode

Or a subtler one — a string that ends prematurely because an unescaped " closes it early.

❌ Wrong

{
  "message": "He said "hello" and left",
  "filePath": "C:\Users\priya\documents\report.pdf",
  "notes": "First line
Second line"
}
Enter fullscreen mode Exit fullscreen mode

Three problems here:

  • Unescaped " inside a string
  • Unescaped backslashes in the Windows path
  • Literal newline inside a string

✅ Fixed

{
  "message": "He said \"hello\" and left",
  "filePath": "C:\\Users\\priya\\documents\\report.pdf",
  "notes": "First line\nSecond line"
}
Enter fullscreen mode Exit fullscreen mode

Full escape reference:

Character Escaped
" \"
\ \\
Newline \n
Tab \t
Carriage return \r
Unicode \uXXXX

The golden rule for dynamic JSON

If you're building JSON strings from user input, always use JSON.stringify() — never concatenate strings manually:

// ❌ Never do this — JSON injection + XSS risk
const json = `{"message": "${userInput}"}`;

// ✅ Do this
const json = JSON.stringify({ message: userInput });
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: Windows file paths are the #1 source of unescaped backslash bugs. If you're storing paths in JSON config files, always double-escape them or use forward slashes — Windows accepts both.


Error #5: Mismatched Brackets or Braces

What's happening

Every { needs a }. Every [ needs a ]. In long or deeply nested JSON, a missing bracket is easy to introduce and hard to spot manually.

The error you'll see

SyntaxError: Unexpected end of JSON input
Enter fullscreen mode Exit fullscreen mode

or

SyntaxError: Expected ',' or '}' after property value in JSON at position 312
Enter fullscreen mode Exit fullscreen mode

❌ Wrong

{
  "pipeline": {
    "stages": [
      {
        "name": "build",
        "steps": ["install", "compile", "test"
      },
      {
        "name": "deploy",
        "steps": ["push", "restart"]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

The steps array in the first stage is missing its closing ].

✅ Fixed

{
  "pipeline": {
    "stages": [
      {
        "name": "build",
        "steps": ["install", "compile", "test"]
      },
      {
        "name": "deploy",
        "steps": ["push", "restart"]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

How to find it fast

Paste your JSON into an online JSON formatter — it highlights exactly which line the error is on and pretty-prints your JSON so the structure is obvious. You can also run a quick check in Node:

node -e "JSON.parse(require('fs').readFileSync('config.json', 'utf8'))" && echo "Valid"
Enter fullscreen mode Exit fullscreen mode

Or use jq:

cat data.json | jq .
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: When writing large JSON by hand, add both the opening and closing bracket before filling in the contents. Same principle as closing HTML tags immediately — far less error-prone than adding the closer after 30 lines of content.


Error #6: Comments in JSON — The Feature That Doesn't Exist

What's happening

Developers coming from JavaScript, Python, or YAML naturally try to add comments to JSON config files. JSON has no comment syntax. None. Not //, not /* */, not #.

The error you'll see

SyntaxError: Unexpected token / in JSON at position 2
Enter fullscreen mode Exit fullscreen mode

❌ Wrong

{
  // Database connection settings
  "host": "db.internal.example.com",
  "port": 5432, /* default Postgres port */
  "poolSize": 10 # max concurrent connections
}
Enter fullscreen mode Exit fullscreen mode

✅ Fix option 1: Remove comments

{
  "host": "db.internal.example.com",
  "port": 5432,
  "poolSize": 10
}
Enter fullscreen mode Exit fullscreen mode

✅ Fix option 2: Use a _comment convention

Some teams use a dedicated key for inline documentation:

{
  "_comment": "Database settings — see runbook for rotation procedure",
  "host": "db.internal.example.com",
  "port": 5432,
  "poolSize": 10
}
Enter fullscreen mode Exit fullscreen mode

Not elegant, but it parses cleanly and is searchable.

✅ Fix option 3: Switch to JSON5 or JSONC

If you control the parsing (e.g., a config file for your own tool), use JSON5 or JSONC:

import JSON5 from 'json5';

const config = JSON5.parse(`{
  // Database connection
  host: "db.internal.example.com",
  port: 5432,
}`);
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: tsconfig.json, .eslintrc.json, and VS Code's settings.json all silently support comments even though they use .json — VS Code's parser strips them. Don't let this fool you into thinking regular JSON.parse() handles them. It won't.


Error #7: Incorrect Data Types Causing Silent Failures

What's happening

JSON supports six types: strings, numbers, booleans, arrays, objects, and null. The problem isn't usually an invalid type — it's passing the right value in the wrong type, especially numbers as strings or booleans as strings.

The error you'll see

No parsing error at all. You get a logic bug — comparisons fail, math returns NaN, or an API returns a cryptic 400 Bad Request.

❌ Wrong

// HTML form data — everything comes back as strings
const formData = {
  userId: "1042",      // Should be a number
  isAdmin: "false",    // Should be a boolean — but "false" is truthy!
  retryCount: "3"      // Should be a number
};

// On the receiving end:
if (data.isAdmin) {
  // "false" is a non-empty string → truthy → this ALWAYS runs!
  grantAdminAccess(data.userId); // 💥 Security bug
}
Enter fullscreen mode Exit fullscreen mode

✅ Fixed

const formData = {
  userId: parseInt(form.userId.value, 10),
  isAdmin: form.isAdmin.checked,  // boolean from checkbox
  retryCount: Number(form.retryCount.value)
};
Enter fullscreen mode Exit fullscreen mode

Or validate and coerce at the boundary with zod:

import { z } from 'zod';

const UserSchema = z.object({
  userId: z.coerce.number().int().positive(),
  isAdmin: z.boolean(),
  retryCount: z.coerce.number().int().min(0).max(10)
});

const parsed = UserSchema.parse(rawFormData);
// Throws a detailed error if types don't match after coercion
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: The "false" string bug is particularly dangerous. In JavaScript, every non-empty string is truthy — if ("false") evaluates to true. Always use strict boolean types when the value will be used in a conditional.


Error #8: Circular References When Serializing Objects

What's happening

A circular reference is when an object contains a reference back to itself (directly or through a chain). JSON.stringify() throws immediately when it encounters one.

The error you'll see

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    |     property 'parent' -> object with constructor 'Object'
    --- property 'child' closes the circle
Enter fullscreen mode Exit fullscreen mode

How it happens in the wild

const order = {
  id: "ORD-9821",
  customer: { name: "Alex Mensah" }
};

order.self = order; // Circular reference!
JSON.stringify(order); // 💥 TypeError
Enter fullscreen mode Exit fullscreen mode

DOM nodes, Express req/res objects, and Sequelize/Mongoose model instances are full of these.

✅ Fix option 1: Use a replacer to skip circular refs

function safeStringify(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) return '[Circular]';
      seen.add(value);
    }
    return value;
  });
}

console.log(safeStringify(order));
// {"id":"ORD-9821","customer":{...},"self":"[Circular]"}
Enter fullscreen mode Exit fullscreen mode

✅ Fix option 2: Use the flatted library for full round-trip support

import { stringify, parse } from 'flatted';

const json = stringify(order);   // Handles circular refs
const restored = parse(json);    // Restores the circular structure
Enter fullscreen mode Exit fullscreen mode

✅ Fix option 3: Serialize only what you need

// Instead of serializing the whole ORM instance:
const orderForAPI = {
  id: order.id,
  customerName: order.customer.name,
  items: order.items.map(i => ({ sku: i.sku, qty: i.quantity }))
};

JSON.stringify(orderForAPI); // Clean, no circular refs
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: This error is extremely common when logging Express request objects in error handlers. Never do logger.info(JSON.stringify(req)). Log req.method, req.url, req.headers, and req.body individually instead.


Error #9: Date Objects — The Serialization Trap

What's happening

JSON.stringify() converts Date objects to ISO 8601 strings automatically. But JSON.parse() does not convert them back. You get a plain string — silently, every time.

The error you'll see

No error at parse time. But later:

const event = JSON.parse(JSON.stringify({ createdAt: new Date() }));

console.log(typeof event.createdAt);    // "string" — not a Date!
console.log(event.createdAt.getTime()); // TypeError: event.createdAt.getTime is not a function
Enter fullscreen mode Exit fullscreen mode

❌ Wrong pattern

const meeting = {
  title: "Sprint Planning",
  scheduledAt: new Date("2025-06-10T09:00:00Z"),
  duration: 60
};

const stored = JSON.stringify(meeting);
const restored = JSON.parse(stored);

// This silently fails — string vs number comparison is NaN
if (restored.scheduledAt > Date.now()) {
  sendReminder(); // Never called
}
Enter fullscreen mode Exit fullscreen mode

✅ Fixed

const restored = JSON.parse(stored);

// Option 1: Parse manually
restored.scheduledAt = new Date(restored.scheduledAt);

// Option 2: Use a reviver function
const restoredSafe = JSON.parse(stored, (key, value) => {
  if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value)) {
    return new Date(value);
  }
  return value;
});

// Option 3: Use date-fns
import { parseISO } from 'date-fns';
const scheduledAt = parseISO(restored.scheduledAt);
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: The cleanest architecture is to never store Date objects in state that crosses a serialization boundary. Use ISO strings throughout your app and only convert to Date when you actually need .getTime(), .toLocaleString(), etc.


Error #10: Large Integers and Precision Loss

What's happening

JavaScript uses 64-bit floating point (IEEE 754) for all numbers. The maximum safe integer is 2⁵³ − 1 (Number.MAX_SAFE_INTEGER = 9007199254740991). Any integer larger than this cannot be represented exactly — and JSON parsing silently rounds it.

The error you'll see

No error. Just silently wrong data:

const response = `{"id": 9007199254740993, "userId": 92233720368547758}`;

const parsed = JSON.parse(response);
console.log(parsed.id);     // 9007199254740992 — off by 1!
console.log(parsed.userId); // 92233720368547760 — completely wrong!
Enter fullscreen mode Exit fullscreen mode

This is a real problem with PostgreSQL 64-bit IDs, Twitter/X Snowflake IDs, and distributed systems.

✅ Fix option 1: Use strings for large IDs

The Twitter/X API famously added an id_str field alongside id for this exact reason:

{
  "id": 9007199254740993,
  "id_str": "9007199254740993",
  "text": "JSON precision bugs are the worst"
}
Enter fullscreen mode Exit fullscreen mode

Always use id_str in client code.

✅ Fix option 2: Pre-process with a regex before parsing

function parseBigInts(jsonString) {
  return JSON.parse(
    jsonString.replace(
      /(":\s*)(-?\d{17,})/g,
      (_, prefix, num) => `${prefix}"${num}"`
    )
  );
}

const safe = parseBigInts(`{"snowflakeId": 927349827349827349}`);
console.log(safe.snowflakeId); // "927349827349827349" — exact string
Enter fullscreen mode Exit fullscreen mode

✅ Fix option 3: Use the json-bigint library

import JSONBig from 'json-bigint';

const parsed = JSONBig.parse(`{"id": 9007199254740993}`);
console.log(parsed.id); // 9007199254740993n — a real BigInt
Enter fullscreen mode Exit fullscreen mode

💡 Pro tip: This bug only manifests for IDs large enough to exceed MAX_SAFE_INTEGER. Your test data (IDs like 1, 42, 1001) will never trigger it. It only hits in production. Always verify your ID generation strategy before assuming small numbers.


Debugging Toolkit

When you're staring at a broken JSON payload, here's your workflow:

1. Validate in Node

node -e "JSON.parse(require('fs').readFileSync('config.json', 'utf8'))" && echo "Valid"
Enter fullscreen mode Exit fullscreen mode

2. Pretty-print with Python (no dependencies)

echo '{"a":1,"b":[1,2,3]}' | python3 -m json.tool
Enter fullscreen mode Exit fullscreen mode

3. Validate + query with jq

cat data.json | jq .
Enter fullscreen mode Exit fullscreen mode

4. Validate in your CI/CD pipeline

# In your package.json scripts:
"validate:config": "node -e \"JSON.parse(require('fs').readFileSync('./config.json'))\"",
Enter fullscreen mode Exit fullscreen mode

Quick Reference Cheat Sheet

JSON CHEAT SHEET
══════════════════════════════════════════════════════════════

  SYNTAX RULES                      VALID TYPES
  ─────────────────────────────     ───────────────────────
  ✅ Double quotes only             String:   "hello"
  ✅ No trailing commas             Number:   42, 3.14
  ✅ No comments                    Boolean:  true, false
  ✅ Balanced brackets/braces       Null:     null
  ✅ Keys must be strings           Array:    [1, 2, 3]
                                    Object:   {"k": "v"}

  INVALID VALUES (use null instead)
  ─────────────────────────────────
  ❌ undefined   ❌ NaN   ❌ Infinity   ❌ Functions

  ESCAPE SEQUENCES IN STRINGS
  ─────────────────────────────────
  \"  →  "       \\  →  \       \n  →  newline
  \t  →  tab     \r  →  carriage return
  \uXXXX  →  Unicode character

  SPECIAL CASES TO WATCH FOR
  ─────────────────────────────────
  • Dates        → Store as ISO strings; parse explicitly
  • Big ints     → Use strings for IDs > 9007199254740991
  • Circular refs → Use replacer or flatted library
  • Type coercion → "false" ≠ false; "3" ≠ 3

══════════════════════════════════════════════════════════════
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

JSON errors almost always fall into one of two camps:

  • Syntax errors — trailing commas, wrong quotes, missing brackets. These are the easiest to fix. A good linter catches them before you run a single line of code. Add JSON validation to your editor, run JSON.parse() inside try/catch everywhere you accept external data.

  • Semantic traps — Dates that lose their type, integers that lose precision, undefined that silently vanishes. These are trickier because they don't throw errors — they silently corrupt your data. The fix is validation at the boundary using zod, ajv, or even a careful manual check before you trust the output of JSON.parse().

The patterns that burn developers the most are #3 (NaN/undefined disappearing silently), #9 (Dates becoming strings), and #10 (large integers rounding). If you add nothing else to your workflow, add a JSON schema validator at every API boundary.


Which of these has bitten you hardest? My personal nemesis is #9 — every single time. Drop the error number in the comments.

Top comments (0)