Unexpected End of JSON Input: What It Means and How to Fix It
The parser ran out of text before it found a complete value, and in most cases that is because there was no text at all.
You call JSON.parse() or response.json() and get SyntaxError: Unexpected end of JSON input. Unlike an unexpected token, this error does not point at a wrong character. It means the parser reached the last character while it was still waiting for a value. Either the text was empty, or it stopped part-way through a document. Telling those two cases apart is the whole job, and it takes one line of logging.
The same error in different environments
| Where you see it | Message |
|---|---|
Chrome, Edge and Node.js, JSON.parse | Unexpected end of JSON input |
Chrome and Edge, response.json() | The same, sometimes prefixed with Failed to execute 'json' on 'Response': |
Chrome, a response with no body at all, such as an opaque no-cors response | Unexpected end of input |
| Firefox | JSON.parse: unexpected end of data at line 1 column 1 of the JSON data |
| Safari | JSON Parse error: Unexpected EOF |
Python, json.loads | Expecting value: line 1 column 1 (char 0) |
When a message mentions line 1, column 1 or character 0, the parser found nothing to read. Empty text is not valid JSON: the shortest valid documents are values such as 0, null, "" or {}.
The usual cause: an empty body or an empty string
Before you look for a bug in the data, check whether there was any data. These are the common sources of an empty string:
- 204 No Content. Many APIs answer a successful
DELETEorPUTwith status 204 and no body.res.okistruefor 204, so checking it does not protect theres.json()call that follows. - HEAD requests. A response to
HEADnever has a body, whatever its headers say. - fetch with
mode: 'no-cors'. This does not get around CORS. It returns an opaque response with status 0 and a body your code is not allowed to read, so parsing it always fails. Enable CORS on the server or call the API through your own backend. - A 200 or 500 with nothing in it. A handler that never writes its output, or a server error that ends the request before anything is sent, produces an empty body with an ordinary status code.
- Empty storage values and files.
localStorage.setItem('cart', ''), an emptysettings.jsonor an environment variable that is set but blank all giveJSON.parsean empty string. - A trailing newline in line-delimited JSON.
text.split('\n').map(JSON.parse)fails on the empty string after the last newline. Filter out blank lines first.
Two look-alikes behave differently. A missing localStorage key returns null, and JSON.parse(null) returns null without throwing. An unset variable is undefined, which fails with "undefined" is not valid JSON instead; that one is covered in Unexpected token in JSON.
To see which case you have, read the body as text before parsing it:
const res = await fetch('/api/cart', { method: 'DELETE' });
const text = await res.text();
console.log(res.status, res.type, res.headers.get('content-type'), text.length);
// Typical results:
// 204 basic null 0 -> nothing to parse
// 0 opaque null 0 -> a no-cors request
// 200 basic application/json 8192 -> there is text: check whether it is complete
In the browser, the Network panel shows the same thing: an empty Response tab means the problem is the request or the server, not your parsing code.
When the text was cut off
If the text is not empty, it ended before the document was complete. Current Chrome and Node.js only say "Unexpected end of JSON input" when the text stops where a value should begin, or in the middle of true, false or null. Anywhere else the same truncation produces a different message:
| Text ends with | Message in current Chrome and Node.js |
|---|---|
{"id": | Unexpected end of JSON input |
{"tags":["a", | Unexpected end of JSON input |
{"ok":tru | Unexpected end of JSON input |
{"name":"Al (11 characters) | Unterminated string in JSON at position 11 |
{"id":42 (8 characters) | Expected ',' or '}' after property value in JSON at position 8 |
The useful rule: if the position in the message equals the length of the text, the document was truncated rather than malformed. Older versions of Chrome and Node.js reported most of these cases as "Unexpected end of JSON input" too, so older logs and answers use that wording for truncation in general.
Truncated JSON usually comes from one of these places:
- Parsing a stream chunk by chunk. Data from
res.body.getReader(), a Node.jsdataevent or a socket arrives in pieces of arbitrary size. A single piece is rarely a complete document. Collect the chunks and parse once at the end, or buffer line-delimited JSON until each newline. - Storage that is too small. A database column shorter than the document, in a database configured to truncate instead of rejecting the write, stores the first part and drops the rest.
- Copying from somewhere that shortens long text, such as a log system with a maximum line length.
- A connection that closed early on a response that had no
Content-Lengthand was not chunked. When the length is declared, a dropped connection is reported as a network error instead: in Node.js,res.json()rejects withTypeError: terminated.
Guarding against it in your code
Read the body as text, decide what an empty body means for this endpoint, and only then parse. This handles 204, HEAD and empty 200 responses in one place and keeps the error message useful when something else goes wrong:
async function readJson(res) {
const text = await res.text();
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${text.slice(0, 100) || '(empty body)'}`);
}
if (text.trim() === '') return null; // 204, HEAD or an empty 200
try {
return JSON.parse(text);
} catch (err) {
throw new Error(`Invalid JSON (${text.length} chars): ${err.message}`);
}
}
For stored values, treat empty and missing the same way and fall back to a default:
function loadCart() {
const raw = localStorage.getItem('cart');
if (!raw) return []; // null (missing) or '' (empty)
try {
return JSON.parse(raw);
} catch {
return []; // damaged value: start again
}
}
On the server side, be consistent: return 204 with no body and no JSON content type, or return a small JSON document such as {} or {"deleted": true}. A 200 response labelled application/json with an empty body is the combination that breaks clients.
Checking the text by hand
When the body is not empty, copy it from the Network panel or your logs and paste it into the JSON Validator. It uses the browser's own parser, so it shows the same message and position your code got, and it formats the document once it is valid. If the reported position is at the very end of what you pasted, look for the missing part in the transport or storage rather than in the data. For errors that name a specific character, such as < from an HTML page, see Unexpected token in JSON.