Unexpected End of JSON Input: Every Cause and Fix
SyntaxError: Unexpected end of JSON input is the sibling error to "Unexpected token in JSON", and in some ways the more frustrating one: it comes with no position number, no character to blame, nothing to search for. That's because the cause isn't a bad character — it's an absence. The parser was still expecting something (a value, another array element, a closing brace) when the input simply ran out. This guide covers every real-world way that happens, the exact V8 message it produces, and the fix, with a live, prefilled JSON Lab example for each.
JSON.parse reaches the end of the string mid-value — typically an empty response body, a network request that got cut off, or a file read while something else was still writing it. If the string instead cuts off in the middle of a quoted string, you'll see a different message, Unterminated string in JSON, not this one — see the reference table below for exactly where the line falls.
Quick reference
Verified against current V8 (the engine behind Chrome, Edge, and Node.js) by running each input directly through JSON.parse. Not every truncated input produces this message — where the cut lands matters more than the fact that it's truncated at all.
| Input | What V8 prints | Why |
|---|---|---|
'' (empty string) | Unexpected end of JSON input | Nothing to parse at all |
{"data": | Unexpected end of JSON input | Cut right after a colon — a value was expected |
[1,2, | Unexpected end of JSON input | Cut right after a comma — another element was expected |
{"name": "GetDev | Unterminated string in JSON | Cut inside a string, not between tokens |
{"a":1, | Expected double-quoted property name in JSON | Cut after a comma inside an object, not an array — a key was expected, not a value |
[1,2 (no comma) | Expected ',' or ']' after array element in JSON | A complete value sits right before the cut, so V8 asks what comes next instead of reporting an abrupt end |
1. An empty or blank response body
The single most common cause. A 204 No Content response, a 200 with Content-Length: 0, or a proxy/CDN that strips the body while leaving the status code intact — in every case, res.json() ends up calling JSON.parse('') internally, and an empty string is the simplest possible way to run out of input.
const res = await fetch('/api/notifications/mark-read', { method: 'POST' });
const data = await res.json();
// SyntaxError: Unexpected end of JSON input
// (the endpoint correctly returns 204 with no body — .json() shouldn't have been called)
The fix is to stop assuming every response has a JSON body. Check Content-Length or the status code before parsing, or read the text first and only parse if it's non-empty:
const res = await fetch('/api/notifications/mark-read', { method: 'POST' });
const text = await res.text();
const data = text ? JSON.parse(text) : null;
See it fail live
A response body that's empty for parsing purposes — nothing but whitespace — produces the identical message. Paste it in and JSON Lab reports the same error a true empty string would.
Open this example in JSON Lab2. A network request cut off mid-transfer
A dropped connection, a proxy timeout, or an AbortController firing mid-download can all leave you with a response body that's a genuine prefix of valid JSON — correct up to the point it stops. Whether that produces this exact message depends on where the cut lands: right after a colon or a comma (waiting on the next token) gives Unexpected end of JSON input; mid-string gives Unterminated string in JSON instead.
// The stream died right after this colon — a value was expected next
const partial = '{"status":"ok","data":';
JSON.parse(partial);
// Unexpected end of JSON input
Guard against this by validating res.ok and checking that the stream actually completed before trusting the body, and wrap the parse in a try/catch that logs the raw text so a truncated payload is visible in your error logs instead of just a generic syntax error:
let text;
try {
text = await res.text();
return JSON.parse(text);
} catch (err) {
console.error('JSON parse failed. Raw response (first 200 chars):', text?.slice(0, 200));
throw err;
}
See it fail live
Open this example in JSON Lab3. Reading a JSON file while something else is still writing it
A config file, a cache file, a build manifest — written with a plain fs.writeFile instead of an atomic write, and read by something else (a dev server watching for changes, a second process) at the wrong moment. The reader sees the file exactly as it exists mid-write: a real, on-disk JSON prefix that simply hasn't finished yet.
// Writer is still mid-flush; reader catches it here:
const onDisk = '{"host": "localhost", "port":';
JSON.parse(onDisk);
// Unexpected end of JSON input
The fix is to make the write atomic: write to a temporary file, then rename it into place. A rename is a single filesystem operation, so a concurrent reader either sees the old complete file or the new complete file — never a partial one.
import { writeFileSync, renameSync } from 'fs';
function writeJsonAtomic(path, data) {
const tmp = `${path}.tmp`;
writeFileSync(tmp, JSON.stringify(data, null, 2));
renameSync(tmp, path); // atomic on the same filesystem
}
See it fail live
Open this example in JSON Lab4. localStorage holding an empty string, not null
localStorage.getItem() returns null for a key that was never set, and JSON.parse(null) quietly succeeds — null coerces to the valid JSON literal "null", so there's no crash. The actual bug is code that "clears" a cached value by writing an empty string instead of removing the key:
// Meant to clear the draft, but this isn't the same as removeItem:
localStorage.setItem('draft', '');
// ...later, elsewhere in the app:
const draft = JSON.parse(localStorage.getItem('draft'));
// Unexpected end of JSON input — getItem returned '', not null
Either use removeItem to actually clear the key, or guard the read against both possibilities:
// Fixed: clear it properly
localStorage.removeItem('draft');
// Fixed: or guard the read against both null and ''
const raw = localStorage.getItem('draft');
const draft = raw ? JSON.parse(raw) : null;
See it fail live
Same failure, shown directly: an empty-for-parsing value where a stored draft should have been.
Open this example in JSON LabDebugging checklist
- Log the raw string's length before parsing. Zero-length or whitespace-only means an upstream empty body, not a malformed one — a completely different fix.
- Check the HTTP status and
Content-Lengthheader. A204, or a200withContent-Length: 0, means the server never intended to send a body — the bug is calling.json()at all, not the JSON itself. - If the string is non-empty, check what's at the very end. A dangling
:or,means a value or element was cut off mid-transfer or mid-write; there's no way to recover it without the source that produced it. - For files, check whether the write is atomic. A reader catching a partial write is a timing bug, not a JSON bug — fixing the parse call won't fix it, only writing atomically will.
- Remember this error has no position number. If you need to know exactly how much valid JSON exists before the cut, run the string through a real parser with a tree view rather than guessing from the message alone.
🧪 Paste the truncated response and see the real error
JSON Lab runs the same parser your browser does — formatting, validation and every example above happen entirely client-side, with nothing uploaded. The one exception is AI Repair: choosing it sends your current input to Gemini to generate a suggested fix, so skip that button for anything sensitive and stick to manual fixes from this guide instead.
Open JSON Lab →