Fix "Unexpected End of JSON Input" Error — Every Cause Explained

SyntaxError: Unexpected end of JSON input means JSON.parse() received an incomplete or empty string. The JSON was cut off before it finished — a missing closing bracket, empty response body, or truncated network response. This guide covers every cause with working fixes: empty strings, API empty responses, truncated JSON, localStorage edge cases, Node.js stream issues, and how to diagnose any occurrence in under 2 minutes.

Empty string

most common cause — JSON.parse("") always throws

API 204/empty

server returns empty body — check Network tab first

2 minutes

to diagnose with browser DevTools Network tab

6 causes

root causes covered with specific fixes

The exact error message

SyntaxError: Unexpected end of JSON input — JavaScript (Chrome/Node.js/Safari) SyntaxError: JSON.parse: unexpected end of data at line 1 column 1 — Firefox The error fires inside JSON.parse() when the input string ends before the JSON structure is complete.
1

Cause 1 — Parsing an Empty or Null String

The most common cause. JSON.parse("") and JSON.parse(null)throw immediately. This happens when an API returns an empty body, a localStorage key doesn't exist, or a variable hasn't been assigned yet.

The empty string rule

JSON.parse("") throws immediately. An empty string is not valid JSON. The most common cause: your API returned an empty body (HTTP 204, network error, or empty response). Always validate the string before parsing: check it exists, is a string type, and is not empty.

Always validate before parsing

Parse without checking — throws on empty

❌ Bad
// Empty string or undefined fed to JSON.parse
const data = JSON.parse("");        // ❌ SyntaxError: Unexpected end of JSON input
const data = JSON.parse(undefined); // ❌ SyntaxError
const data = JSON.parse(null);      // Returns null — special case, doesn't throw!
const data = JSON.parse("  ");      // ❌ SyntaxError — whitespace-only string

Validate and try/catch — returns fallback gracefully

✅ Good
// Safe JSON parse with validation and fallback
function safeJsonParse(str, fallback = null) {
  if (!str || typeof str !== 'string') return fallback;
  const trimmed = str.trim();
  if (!trimmed) return fallback;  // empty or whitespace-only
  try {
    return JSON.parse(trimmed);
  } catch (err) {
    console.error('[safeJsonParse] Failed:', err.message, 'Input:', str.slice(0, 100));
    return fallback;
  }
}

// Usage:
const data = safeJsonParse(rawResponse, []);     // default to empty array
const config = safeJsonParse(configStr, {});     // default to empty object
const value = safeJsonParse(localStorage.getItem('key'), null);
2

Cause 2 — API Returned an Empty Response Body

When a fetch/axios request returns a 204 No Content, an error response with no body, or a network failure, the response body is empty. Calling .json() on an empty body triggers this error.

Check the body before parsing

response.json() throws on empty body

❌ Bad
// response.json() on an empty body → SyntaxError
const response = await fetch('/api/users/123');
const user = await response.json(); // throws if body is empty (204, network error, etc.)

Read as text first, validate, then parse

✅ Good
// Read as text first, check content, then parse
const response = await fetch('/api/users/123');

// Check HTTP status first
if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

// Check if there's actually a body
const text = await response.text();
if (!text.trim()) {
  console.warn('API returned empty body for', response.url);
  return null;  // or handle empty as appropriate
}

// Now safe to parse
let data;
try {
  data = JSON.parse(text);
} catch (err) {
  console.error('API response was not valid JSON:', text.slice(0, 200));
  throw err;
}
return data;
3

Cause 3 — Truncated or Malformed JSON

Examples of truncated JSON that trigger this error

{"name": "Alice", "age": ← cut off mid-value[1, 2, 3, ← missing closing bracket{"items": [{"id": 1}, {"id": 2 ← never closed{"status": "ok", "data": ← value never provided

Check the raw response in DevTools → Network tab → click the request → Response tab. If the JSON is visibly cut off, the issue is server-side: a buffer overflow, timeout mid-response, or server crash while streaming the response body.

Server crash mid-response

The server starts sending JSON but crashes or times out before completing. Check your server logs for the time of the request. Look for unhandled exceptions or OOM errors on the server. The Network tab shows truncated content.

Response size limit hit

Some proxies, load balancers, or CDNs have response body size limits. If your response exceeds the limit, it gets cut off. Check the Content-Length header vs the actual bytes received.

Network interruption

Mobile connections or flaky networks can drop mid-response. The client receives partial data. Implement retry logic for network errors. Check the Network tab for failed or cancelled requests.

Streaming response not fully consumed

If using Node.js streams or Response.body streams, partially reading the stream gives truncated data. Always read the full stream before parsing.

4

Cause 4 — Reading a File That Doesn't Exist or Is Empty

Read and validate file content before parsing

No validation — crashes on missing or empty file

❌ Bad
const fs = require('fs');
// If file doesn't exist → readFileSync throws ENOENT
// If file exists but is empty → JSON.parse("") throws
const data = JSON.parse(fs.readFileSync('config.json', 'utf8'));

Check existence, check emptiness, wrap in try/catch

✅ Good
const fs = require('fs');
const path = require('path');

function readJsonFile(filePath, fallback = null) {
  const absolutePath = path.resolve(filePath);

  // Check existence first
  if (!fs.existsSync(absolutePath)) {
    console.error('File not found:', absolutePath);
    return fallback;
  }

  const content = fs.readFileSync(absolutePath, 'utf8');

  if (!content.trim()) {
    console.error('File is empty:', absolutePath);
    return fallback;
  }

  try {
    return JSON.parse(content);
  } catch (err) {
    console.error('Invalid JSON in file:', absolutePath, err.message);
    return fallback;
  }
}

const config = readJsonFile('./config.json', {});  // safe
5

Cause 5 — localStorage Returns null

Check localStorage value before parsing

Parse null directly — throws immediately

❌ Bad
// localStorage.getItem() returns null if the key doesn't exist
const prefs = JSON.parse(localStorage.getItem('userPrefs')); // null → SyntaxError

Check for null before parsing — use fallback

✅ Good
// Pattern 1: null check before parsing
const raw = localStorage.getItem('userPrefs');
const prefs = raw ? JSON.parse(raw) : {};  // default to empty object

// Pattern 2: use safeJsonParse for full protection
const prefs = safeJsonParse(localStorage.getItem('userPrefs'), {});

// Pattern 3: utility function that wraps the whole get+parse flow
function getFromStorage(key, fallback = null) {
  try {
    const item = localStorage.getItem(key);
    return item !== null ? JSON.parse(item) : fallback;
  } catch {
    localStorage.removeItem(key);  // clear corrupted data
    return fallback;
  }
}

const prefs = getFromStorage('userPrefs', { theme: 'dark', fontSize: 14 });
6

Cause 6 — Parsing Chunks of a Node.js Stream

Buffer the full request body before parsing

Parse each chunk — partial JSON throws

❌ Bad
// ❌ Each data chunk may be only part of the full JSON body
req.on('data', (chunk) => {
  const data = JSON.parse(chunk);  // chunk = partial JSON → throws
  // ...
});

Buffer all chunks, parse once at end

✅ Good
// ✅ Buffer all chunks, then parse complete body at end
let body = '';
req.on('data', (chunk) => {
  body += chunk.toString();  // accumulate chunks
});
req.on('end', () => {
  if (!body.trim()) {
    return res.status(400).json({ error: 'Empty request body' });
  }
  try {
    const data = JSON.parse(body);  // parse complete JSON
    // process data...
  } catch (err) {
    res.status(400).json({ error: 'Invalid JSON in request body' });
  }
});
req.on('error', (err) => {
  console.error('Request error:', err);
  res.status(500).end();
});

// Better: use express.json() middleware (handles all of the above automatically)
app.use(express.json());  // no manual buffering needed
7

Diagnostic Steps — Find the Root Cause in 2 Minutes

1

Log the raw value before parsing

Add: console.log("About to parse:", JSON.stringify(rawValue).slice(0, 500)) immediately before the JSON.parse() call. This shows exactly what string you're parsing.

2

Check Network tab response body

Open DevTools → Network → find the API request → Response tab. Is the body empty? Is it HTML (an error page) instead of JSON? Is the JSON visibly cut off? This identifies server-side issues immediately.

3

Check response Content-Length

Network → Headers tab. Compare the Content-Length header value to the actual response body size shown. If they don't match, the response was truncated by a proxy or network issue.

4

Look at the HTTP status code

A 204 No Content or 0 (network failure) legitimately has an empty body. A 200 with an empty body is a server bug. A 500 may return an HTML error page instead of JSON.

5

Wrap in try/catch with full error logging

Add: try { JSON.parse(str) } catch(e) { console.error("Parse failed. Input was:", typeof str, str?.slice(0, 500)) } to capture the exact failing input in your logs.

Frequently Asked Questions