← Blog

Fix JSON Errors: The Complete Developer Guide

Parse errors, unexpected token <, stringify issues, trailing commas, single quotes — every JSON error explained and fixed · 18 min read

JSON errors are among the most common bugs in web development. Whether you are fetching data from an API, reading from localStorage, or building a configuration system, a single malformed character will throw a SyntaxError and break your application. This guide covers every JSON error you will realistically encounter — what causes it, how to diagnose it, and the exact code to fix it.

70%

JSON errors are syntax issues

#1

cause: missing double quotes

token <

usually means HTML response

100%

preventable with try-catch

What is JSON and Why Is It So Strict?

JSON (JavaScript Object Notation) was designed by Douglas Crockford in the early 2000s as a lightweight data interchange format. Its simplicity is its strength — and also the reason it is so unforgiving. JSON has exactly six value types: string, number, boolean, null, array, and object. Everything else is invalid.

Unlike JavaScript objects, JSON has no notion of undefined, NaN, Infinity, functions, symbols, or comments. This strictness is intentional — JSON must be parseable by any language, not just JavaScript.

JSON vs JavaScript Object: Key Syntax Differences

FeatureJSONJavaScript Object
Key quotesRequired (double quotes only)Optional for identifiers
String quotesDouble quotes onlySingle, double, or backtick
Trailing commasNOT allowedAllowed in modern JS
CommentsNOT allowed// and /* */ allowed
undefined valuesNOT validValid property value
NaN / InfinityNOT validValid number values
FunctionsNOT allowedAllowed as values
Date objectsSerialized as stringNative Date object

Mental model: JSON is a subset of YAML and a strict subset of JavaScript

When you write JSON by hand, imagine you are writing for a parser that has zero tolerance for deviation. Every key must be quoted. No trailing commas. No comments. If in doubt, run it through a validator before shipping.

Overview: The 6 Most Common JSON Errors

Common JSON Error Types

SyntaxError: Unexpected token

Invalid character in JSON — trailing comma, single quote, unescaped character, or comment.

Unexpected token < at position 0

Server returned HTML (error page, login redirect) instead of JSON.

Unexpected end of JSON input

JSON string was truncated — network timeout, size limit, or incomplete generation.

JSON.stringify drops keys

Properties with undefined values are silently omitted by JSON.stringify.

Circular reference error

Object contains a reference to itself — JSON.stringify cannot serialize circular structures.

Deeply nested parse failure

JSON string exceeds parser depth limit, or has non-UTF-8 encoding issues.

How JSON.parse() processes your data

Your Code

Calls JSON.parse(str)

Tokenizer

Reads characters one by one

Parser

Builds object tree

SyntaxError?

Invalid token found

Debug

Read error + position

Fix & Retry

Correct the JSON

Error 1: SyntaxError — Unexpected Token (Bad Syntax)

The most frequent JSON error. The parser hit a character it did not expect. The error message usually includes the position in the string where parsing failed, which is your primary debugging clue.

1a. Trailing Commas

JSON does not allow a comma after the last element in an array or the last property in an object. This is the single most common mistake because JavaScript and most modern languages do allow trailing commas.

Trailing comma in object

❌ Bad
// ❌ SyntaxError: Unexpected token }
JSON.parse('{"name": "Alice", "age": 30,}');

// ❌ Also invalid in arrays
JSON.parse('[1, 2, 3,]');
✅ Good
// ✅ Remove the trailing comma
JSON.parse('{"name": "Alice", "age": 30}');

// ✅ Arrays too
JSON.parse('[1, 2, 3]');

1b. Single Quotes Instead of Double Quotes

JSON requires double quotes for both keys and string values. Single quotes are a syntax error. This often happens when developers copy JavaScript object literal syntax into a JSON context.

Single quotes are not valid JSON

❌ Bad
// ❌ Single quotes on keys — SyntaxError
JSON.parse("{'name': 'Alice'}");

// ❌ Mixed quotes
JSON.parse('{"name": 'Alice'}');
✅ Good
// ✅ Always use double quotes
JSON.parse('{"name": "Alice"}');

// ✅ In a JS string, escape the inner double quotes
const raw = '{"name": "Alice", "city": "New York"}';
const parsed = JSON.parse(raw);

1c. Comments in JSON

JSON has no comment syntax. Any // or /* */ will cause a SyntaxError. If you need comments in config files, use JSON5, HJSON, or YAML instead.

Comments cause SyntaxError

❌ Bad
// ❌ Comments are NOT allowed in JSON
{
  // User configuration
  "theme": "dark",
  /* Set to true to enable notifications */
  "notifications": true
}
✅ Good
// ✅ Remove all comments from JSON
{
  "theme": "dark",
  "notifications": true
}

// ✅ If you need comments, use JSON5 format
// and the json5 npm package to parse it

1d. Unescaped Special Characters

jsonCharacters that must be escaped inside JSON strings
// Inside a JSON string, these characters must be escaped:
{
  "path": "C:\\Users\\Alice\\Documents",   // \ → \\
  "quote": "She said \"hello\"",               // " → \"
  "newline": "line1\nline2",                    // newline → \n
  "tab": "col1\tcol2",                          // tab → \t
  "emoji": "Works fine: \u2764",                // Unicode OK
  "null_byte": "NOT OK in plain strings"         // \u0000 needs escape
}

// ✅ Use JSON.stringify to generate valid JSON from JS values:
const obj = { message: 'She said "hello"', path: 'C:\Users\Alice' };
const safe = JSON.stringify(obj);
// '{"message":"She said \"hello\"","path":"C:\\Users\\Alice"}'

NaN and Infinity are not valid JSON

JSON.stringify(NaN) returns "null", and JSON.stringify(Infinity) also returns "null". This is silent data loss. Always validate numeric values before serializing them if you rely on their exact value.

Error 2: "Unexpected token <" — API Returns HTML

This is arguably the most confusing JSON error because it is not really a JSON problem at all — it is a network/server problem. The < character is the first character of an HTML document (<!DOCTYPE or <html). When you see this error, your server returned an HTML page instead of JSON.

Why does the server return HTML instead of JSON?

404 Not Found

Wrong URL — the server returns its 404 HTML error page.

401/403 Unauthorized

Missing or expired auth token — server redirects to login page.

500 Server Error

Backend crash — server returns its default HTML error page.

Wrong endpoint

Hitting the web app URL instead of the API endpoint.

Redirect to login

Session expired, server issues 302 redirect to login HTML.

CDN/WAF block

Cloudflare or WAF returns an HTML block page.

Fix: Check Content-Type before calling .json()

❌ Bad
// ❌ Blindly calling .json() — will throw if HTML is returned
const res = await fetch('/api/users');
const data = await res.json(); // 💥 SyntaxError if server returned HTML

// ❌ Also bad: not checking response.ok
const res = await fetch('/api/users');
if (res.ok) {
  const data = await res.json(); // Still might return HTML on 4xx
}
✅ Good
// ✅ Always check Content-Type and response.ok
async function fetchJSON(url, options) {
  const res = await fetch(url, options);

  // Check content type FIRST
  const ct = res.headers.get('content-type') ?? '';
  if (!ct.includes('application/json')) {
    const text = await res.text();
    throw new Error(
      `Expected JSON but got ${ct}. Status: ${res.status}. Body: ${text.slice(0, 200)}`
    );
  }

  if (!res.ok) {
    const err = await res.json().catch(() => ({ message: 'Unknown error' }));
    throw new Error(`HTTP ${res.status}: ${err.message ?? JSON.stringify(err)}`);
  }

  return res.json();
}

// Usage
const users = await fetchJSON('/api/users');

How to debug this error in DevTools

Open DevTools → Network tab → find the failing request → click it → look at the Response tab. You will see the actual HTML that the server returned. The Status code (404, 401, 500) tells you exactly what went wrong. Fix the underlying server issue, not the JavaScript.

Error 3: "Unexpected End of JSON Input"

This error means the JSON string ended before the parser finished building the object tree. Imagine receiving {"name": "Alice — the parser opened an object, found the key "name", started reading the string value "Alice", but then hit the end of the string before finding the closing quote, brace, or bracket.

Common causes of truncated JSON

1

Network timeout or interruption

Most common

The HTTP connection dropped mid-response. The browser received only part of the response body. Check for network errors in DevTools.

2

Response size limit hit

A proxy, CDN, or serverless function truncated the response at a size limit (e.g., Lambda 6MB limit). Switch to streaming or pagination.

3

String was accidentally sliced

Code somewhere called str.slice() or str.substring() on the raw JSON string, cutting it short. Search your codebase for any string manipulation on API responses.

4

Server-side generation bug

The backend code generating JSON crashed mid-write and sent a partial response without proper error handling. Check server logs.

5

Empty response body

The server returned HTTP 204 No Content or an empty body, and the code called .json() expecting a payload. Check for empty string before parsing.

typescriptRobust fetch wrapper handling all these cases
async function fetchJSONSafe<T>(url: string, options?: RequestInit): Promise<T> {
  let res: Response;
  try {
    res = await fetch(url, options);
  } catch (networkError) {
    // DNS failure, timeout, connection refused
    throw new Error(`Network error: ${(networkError as Error).message}`);
  }

  // Handle 204 No Content
  if (res.status === 204) return undefined as T;

  const text = await res.text();

  // Guard against empty body
  if (!text || text.trim() === '') {
    if (!res.ok) throw new Error(`HTTP ${res.status} with empty body`);
    return undefined as T;
  }

  // Check Content-Type
  const ct = res.headers.get('content-type') ?? '';
  if (!ct.includes('application/json') && !ct.includes('text/json')) {
    throw new Error(
      `Expected JSON, got "${ct}". First 200 chars: ${text.slice(0, 200)}`
    );
  }

  let data: T;
  try {
    data = JSON.parse(text);
  } catch (parseError) {
    throw new Error(
      `JSON parse failed: ${(parseError as Error).message}. ` +
      `Raw (first 500 chars): ${text.slice(0, 500)}`
    );
  }

  if (!res.ok) {
    const errMsg = (data as any)?.message ?? (data as any)?.error ?? JSON.stringify(data);
    throw new Error(`HTTP ${res.status}: ${errMsg}`);
  }

  return data;
}

Error 4: JSON.stringify Silently Drops undefined

This is a subtle bug that does not throw an error — it causes silent data loss. When JSON.stringify encounters an object property whose value is undefined, it simply skips the key. The resulting JSON string will not contain that key at all. If you later parse it, the key will be missing.

JSON.stringify behavior with undefined, NaN, Infinity

❌ Bad
// ❌ These values are silently dropped or converted
const user = {
  id: 1,
  name: 'Alice',
  avatar: undefined,     // ← dropped entirely
  score: NaN,            // ← becomes null
  ratio: Infinity,       // ← becomes null
};

const json = JSON.stringify(user);
// '{"id":1,"name":"Alice","score":null,"ratio":null}'
// Note: "avatar" key is completely missing!

const parsed = JSON.parse(json);
console.log('avatar' in parsed); // false — data lost!
✅ Good
// ✅ Option 1: Use null explicitly for "no value"
const user = {
  id: 1,
  name: 'Alice',
  avatar: null,          // ← null IS valid JSON
};
JSON.stringify(user);
// '{"id":1,"name":"Alice","avatar":null}'

// ✅ Option 2: Use a replacer to convert undefined → null
function undefinedToNull(_key: string, value: unknown) {
  return value === undefined ? null : value;
}
JSON.stringify(user, undefinedToNull);

// ✅ Option 3: Use a custom serializer for NaN/Infinity
function numberReplacer(_key: string, value: unknown) {
  if (typeof value === 'number') {
    if (Number.isNaN(value)) return 'NaN';
    if (!Number.isFinite(value)) return value > 0 ? 'Infinity' : '-Infinity';
  }
  return value;
}
JSON.stringify({ score: NaN, ratio: Infinity }, numberReplacer);
// '{"score":"NaN","ratio":"Infinity"}' — strings, but at least not lost

JSON.stringify array behavior with undefined

In arrays, undefined is not dropped — it is converted to null:
JSON.stringify([1, undefined, 3]) produces [1,null,3]. This is different from object behavior where the key is omitted entirely. Both cases are silent and can cause confusing bugs.

Error 5: Circular Reference — JSON.stringify Fails

If an object contains a reference back to itself (directly or through a chain), JSON.stringify will throw a TypeError: Converting circular structure to JSON. This often happens with DOM nodes, Express req/res objects, or complex graph-like data structures.

typescriptDetecting and handling circular references
// ❌ This throws TypeError
const obj: any = { name: 'Alice' };
obj.self = obj;  // circular reference!
JSON.stringify(obj); // TypeError: Converting circular structure to JSON

// ✅ Option 1: Use a WeakSet to detect cycles
function safeStringify(obj: unknown, indent?: number): string {
  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;
  }, indent);
}

// ✅ Option 2: Use the 'flatted' or 'json-stringify-safe' npm package
import stringify from 'json-stringify-safe';
const safe = stringify(circularObj, null, 2);

// ✅ Option 3: For logging, use util.inspect (Node.js)
import util from 'util';
console.log(util.inspect(circularObj, { depth: 4, circular: true }));

Try-Catch Patterns: Never Let JSON.parse Crash Your App

JSON.parse() throws a synchronous SyntaxError on invalid input. If you do not catch it, the error will propagate up and potentially crash your application or render a broken UI. Wrapping JSON.parse in try-catch is not optional in production code — it is mandatory.

The JSON debugging workflow

1

Read the exact error message

Step 1

The error message includes the position: "at position 42". That position points to the character that broke the parser. Count from the start of the string.

2

Log the raw string

Step 2

Before parsing, log the raw string (or the first 500 characters). This reveals whether the server sent HTML, an empty string, or malformed JSON.

3

Check Content-Type header

Step 3

If the Content-Type is text/html instead of application/json, the server returned an error page. Fix the server issue, not the JavaScript.

4

Use a JSON validator

Step 4

Paste the raw string into a JSON validator. It will highlight exactly which character is invalid and why. Our JSON Fixer can often auto-correct common errors.

5

Fix the source, not the symptom

Step 5

If the server is sending bad JSON, fix the server. If you are hardcoding JSON strings, use JSON.stringify() to generate them instead of writing them by hand.

typescriptProduction-ready JSON utility functions
// ── 1. Safe parse with fallback ──────────────────────────────
export function parseJSON<T>(raw: string, fallback: T): T {
  try {
    return JSON.parse(raw) as T;
  } catch {
    return fallback;
  }
}

// ── 2. Safe parse that returns Result type ────────────────────
type ParseResult<T> =
  | { ok: true; data: T }
  | { ok: false; error: string; raw: string };

export function parseJSONResult<T>(raw: string): ParseResult<T> {
  try {
    return { ok: true, data: JSON.parse(raw) as T };
  } catch (e) {
    return {
      ok: false,
      error: (e as SyntaxError).message,
      raw: raw.slice(0, 200),
    };
  }
}

// Usage:
const result = parseJSONResult<User>(apiResponse);
if (!result.ok) {
  console.error('Parse failed:', result.error, 'Raw:', result.raw);
  showErrorToUser('Failed to load data. Please refresh.');
} else {
  setUser(result.data);
}

// ── 3. localStorage safe helpers ─────────────────────────────
export function getLocalStorageJSON<T>(key: string, fallback: T): T {
  try {
    const raw = localStorage.getItem(key);
    if (raw === null) return fallback;
    return JSON.parse(raw) as T;
  } catch {
    // localStorage had corrupted data — clear and return fallback
    localStorage.removeItem(key);
    return fallback;
  }
}

export function setLocalStorageJSON(key: string, value: unknown): boolean {
  try {
    localStorage.setItem(key, JSON.stringify(value));
    return true;
  } catch {
    // Quota exceeded or value is not serializable
    return false;
  }
}

Real-World Examples: JSON Errors in Common Scenarios

React: Fetch and Parse API Response

tsxReact hook with proper JSON error handling
import { useState, useEffect } from 'react';

interface ApiResponse<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

export function useAPI<T>(url: string): ApiResponse<T> {
  const [state, setState] = useState<ApiResponse<T>>({
    data: null,
    loading: true,
    error: null,
  });

  useEffect(() => {
    let cancelled = false;

    async function load() {
      setState(s => ({ ...s, loading: true, error: null }));
      try {
        const res = await fetch(url);

        // Check content type before parsing
        const ct = res.headers.get('content-type') ?? '';
        if (!ct.includes('application/json')) {
          const text = await res.text();
          throw new Error(
            `Server returned non-JSON (${ct}). ` +
            `Status: ${res.status}. Body: ${text.slice(0, 100)}`
          );
        }

        const data: T = await res.json();

        if (!cancelled) {
          setState({ data, loading: false, error: null });
        }
      } catch (e) {
        if (!cancelled) {
          setState({ data: null, loading: false, error: (e as Error).message });
        }
      }
    }

    load();
    return () => { cancelled = true; };
  }, [url]);

  return state;
}

// Component usage
function UserProfile({ userId }: { userId: string }) {
  const { data: user, loading, error } = useAPI<User>(`/api/users/${userId}`);

  if (loading) return <Spinner />;
  if (error) return <ErrorBanner message={error} />;
  return <div>{user?.name}</div>;
}

Node.js: Parse Request Body

typescriptExpress.js endpoint with JSON validation
import express from 'express';
import { z } from 'zod'; // zod for schema validation

const app = express();
app.use(express.json({
  // Handle JSON parse errors at the middleware level
  strict: true,
  limit: '1mb',
}));

// Handle JSON parse errors from express.json() middleware
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (err.type === 'entity.parse.failed') {
    return res.status(400).json({
      error: 'Invalid JSON in request body',
      detail: err.message,
    });
  }
  next(err);
});

// Define schema with zod
const CreateUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

app.post('/api/users', (req, res) => {
  const result = CreateUserSchema.safeParse(req.body);
  if (!result.success) {
    return res.status(422).json({
      error: 'Validation failed',
      details: result.error.format(),
    });
  }
  const { name, email, age } = result.data;
  // ... create user
  res.status(201).json({ id: 'new-id', name, email, age });
});

Prevention: How to Write JSON That Never Breaks

Golden rule: never handwrite JSON strings in production code

Use JSON.stringify() to generate JSON from JavaScript objects. It handles all escaping, quoting, and serialization correctly. Only handwrite JSON in configuration files, and always validate them with a JSON validator.

JSON error prevention checklist

Never handwrite JSON strings

Use JSON.stringify() to generate JSON. Manual JSON is error-prone.

Always wrap JSON.parse in try-catch

Treat all external JSON as untrusted. Never assume it is valid.

Check Content-Type before parsing

Verify the server returned application/json before calling .json().

Validate schemas with Zod or Yup

Even valid JSON may have wrong types. Use runtime schema validation.

Use TypeScript for JSON types

Define interfaces for your API responses to catch type mismatches.

Lint JSON files in CI

Use jq or a JSON lint step to catch config file errors before deployment.

bashUseful command-line tools for JSON debugging
# Validate a JSON file with jq
jq . config.json

# Pretty-print a JSON file
jq . raw.json | less

# Query nested data
curl -s https://api.example.com/users | jq '.data[0].name'

# Check if an API returns JSON (curl)
curl -I https://api.example.com/users
# Look for: Content-Type: application/json

# Validate JSON string in Node.js
node -e "JSON.parse(require('fs').readFileSync('data.json', 'utf8')); console.log('Valid!')"

# Fix common JSON issues with python
python3 -c "import json,sys; json.dump(json.load(sys.stdin), sys.stdout, indent=2)"

# Install jsonlint globally for nicer errors
npm i -g jsonlint
jsonlint config.json

Paste any JSON and instantly see which line has the error, with a clear explanation of what is wrong.

Validate JSON →

Auto-fix trailing commas, single quotes, comments, and other common JSON errors in one click.

Fix My JSON →

Frequently Asked Questions

Share this article with Your Friends, Collegue and Team mates

Stay Updated

Get the latest tool updates, new features, and developer tips delivered to your inbox.

Occasional useful updates only. Unsubscribe in one click — we never sell your email.

Feedback for Fix JSON Errors Guide

Tell us what's working, what's broken, or what you wish we built next — it directly shapes our roadmap.

You make the difference

Good feedback is gold — a rough edge you hit today could be smoother for everyone tomorrow.

  • Feature ideas often jump the queue when lots of you ask.
  • Bug reports with steps get fixed faster — paste URLs or examples if you can.
  • Name and email are optional; we won't use them for anything except replying if needed.