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 itMessage
Chrome, Edge and Node.js, JSON.parseUnexpected 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 responseUnexpected end of input
FirefoxJSON.parse: unexpected end of data at line 1 column 1 of the JSON data
SafariJSON Parse error: Unexpected EOF
Python, json.loadsExpecting 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 DELETE or PUT with status 204 and no body. res.ok is true for 204, so checking it does not protect the res.json() call that follows.
  • HEAD requests. A response to HEAD never 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 empty settings.json or an environment variable that is set but blank all give JSON.parse an 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 withMessage in current Chrome and Node.js
{"id":Unexpected end of JSON input
{"tags":["a",Unexpected end of JSON input
{"ok":truUnexpected 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.js data event 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-Length and was not chunked. When the length is declared, a dropped connection is reported as a network error instead: in Node.js, res.json() rejects with TypeError: 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.

Frequently Asked Questions

What does "Unexpected end of JSON input" mean?

The parser reached the end of the text while it still expected a value. Usually the text was empty, for example a 204 response or an empty stored value. Sometimes the document was cut off part-way.

Why does response.json() fail when the request succeeded?

Success does not mean there is a body. A 204 No Content response has ok set to true and no body at all. Read the body with res.text() and only parse it when it is not empty.

Does JSON.parse(null) throw this error?

No. null is converted to the text "null", which is valid JSON, so JSON.parse(null) returns null. An empty string throws "Unexpected end of JSON input", and undefined throws a different error.

How can I tell an empty body from a truncated one?

Log the text length before parsing. Zero means empty. If the length is greater than zero and the error position equals the length, the document was cut off.