SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data means the parser already found a complete JSON value, then found another non-whitespace character after it.
That detail matters. This is not usually a missing comma or a bad quote inside the object. It means the input is too long for one JSON document.
Strict JSON has exactly one top-level value. The value can be an object, array, string, number, boolean, or null, and it may be followed by ordinary JSON whitespace. It cannot be followed by another object, a second array, a semicolon, debug text, HTML, a log footer, or another protocol message.
Quick Diagnosis
When I see this error in a JavaScript app, I start with one question: what character sits at the reported position?
| Raw input shape | What probably happened | Correct fix |
|---|---|---|
{"id":1}{"id":2} |
Two JSON documents were concatenated | Wrap them in an array, or parse them as separate messages |
{"event":"login"}\n{"event":"logout"} |
NDJSON or JSON Lines was parsed as one JSON document | Split by line and parse each line |
{"ok":true}; |
A JavaScript-style semicolon was left after JSON | Remove the semicolon at the producer or before validation |
{"ok":true}done |
Debug text or a log suffix was appended | Stop mixing logs with the JSON body |
{"ok":true}<html> |
A wrapper, proxy, or copied page added markup | Inspect the raw response and fix the endpoint or copy source |
{"jsonrpc":"2.0"}Content-Length: ... |
A framed protocol stream was read as one blob | Read one framed message at a time |
Calling trim() rarely fixes this. Whitespace after a JSON value is already allowed. This error is about the first character after the value that is not whitespace.
What the Error Looks Like
Different parsers use different wording, but they are pointing at the same bug.
// Firefox
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data
at line 1 column 18 of the JSON data
// Chrome, Edge, Node.js, and other V8 runtimes
SyntaxError: Unexpected non-whitespace character after JSON at position 17
// Older V8 wording
SyntaxError: Unexpected token { in JSON at position 17
// Python's analogous error
json.decoder.JSONDecodeError: Extra data: line 1 column 18 (char 17)
In V8, the reported position is a zero-based character index. In Firefox, the message often gives a one-based line and column. Either way, the useful clue is the same: the pointer is usually on the first extra character after the valid JSON value.
Find the Extra Character First
Before repairing anything, print a short preview around the error. Do not log full production payloads if they contain tokens, customer data, cookies, or private records. A 60-character window is usually enough.
function previewJsonParseError(text, error) {
const match = /position (\d+)/.exec(error.message);
if (!match) {
return { message: error.message };
}
const index = Number(match[1]);
const start = Math.max(0, index - 30);
const end = Math.min(text.length, index + 30);
return {
message: error.message,
index,
before: JSON.stringify(text.slice(start, index)),
character: JSON.stringify(text[index]),
after: JSON.stringify(text.slice(index + 1, end)),
};
}
try {
JSON.parse(rawText);
} catch (error) {
console.log(previewJsonParseError(rawText, error));
}
For this input:
{"id":1}{"id":2}
the preview will point at the second {. That tells you the first object is valid and the second object is the extra data.
Cause 1: Two JSON Objects Were Stuck Together
This is the classic version of the error:
{"id":1,"name":"Ada"}{"id":2,"name":"Linus"}
Both objects are valid by themselves. Together, they are not one JSON document.
If you meant "a list of users", make the list explicit:
[
{
"id": 1,
"name": "Ada"
},
{
"id": 2,
"name": "Linus"
}
]
If you meant "two separate messages", do not wrap them just to silence the parser. Parse one message at a time. That is the difference between fixing syntax and changing the protocol.
I would not solve this with a regex like "find the first closing brace and cut there." That breaks as soon as a string contains }:
{
"message": "worker printed } while rendering"
}
Use a real parser, a real delimiter, or a real framing layer.
Cause 2: NDJSON or JSON Lines Was Parsed as One Blob
Logs, exports, queues, and streaming APIs often use one JSON value per line. That format is usually called NDJSON or JSON Lines.
{"time":"2026-05-27T10:00:00Z","event":"login","userId":42}
{"time":"2026-05-27T10:01:13Z","event":"logout","userId":42}
This is useful data, but it is not one JSON document. JSON.parse(text) reads the first line, sees a newline, then sees the { that starts the next line. That second { is the unexpected non-whitespace character.
Parse it line by line:
function parseNdjson(text, { allowBlankLines = true } = {}) {
const rows = [];
const lines = text.split(/\r?\n/);
for (let i = 0; i < lines.length; i += 1) {
const line = lines[i];
if (line.trim() === "") {
if (allowBlankLines) continue;
throw new SyntaxError(`Blank line at NDJSON line ${i + 1}`);
}
try {
rows.push(JSON.parse(line));
} catch (error) {
throw new SyntaxError(
`Invalid JSON on NDJSON line ${i + 1}: ${error.message}`,
);
}
}
return rows;
}
Use JSON.parse() on the whole file only when the file contains one JSON value, for example a single array:
[
{ "event": "login" },
{ "event": "logout" }
]
Cause 3: Debug Text Was Appended to the Payload
This often shows up after a server-side handler, proxy, shell command, or test fixture starts printing extra output.
{"ok":true,"requestId":"req_123"}handled in 23ms
The JSON part is valid. The suffix is not.
In API code, fix the producer. Do not make every client responsible for stripping "handled in 23ms" from a response body. Logs belong in stdout, stderr, structured logging, response headers, or observability tools, not after the JSON response.
In CLI scripts, keep machine-readable output separate from human logs:
// Good: JSON goes to stdout.
process.stdout.write(JSON.stringify(result));
// Good: diagnostics go to stderr.
console.error(`handled in ${elapsedMs}ms`);
That separation lets another program safely run the command and parse stdout as JSON.
Cause 4: A Fetch Wrapper Parsed the Wrong Body
This error can happen in browser and Node fetch code when the response body contains valid JSON plus extra content. It is less common than Unexpected token <, but the debugging pattern is similar: inspect status, content type, final URL, and a short body preview before parsing.
async function readJsonResponse(response) {
const text = await response.text();
const contentType = response.headers.get("content-type") || "";
if (!response.ok) {
throw new Error(`HTTP ${response.status} from ${response.url}`);
}
if (!contentType.toLowerCase().includes("application/json")) {
throw new Error(`Expected JSON but received ${contentType || "unknown"}`);
}
try {
return JSON.parse(text);
} catch (error) {
const preview = text.slice(0, 120).replace(/\s+/g, " ");
throw new SyntaxError(`${error.message}; body starts with ${preview}`);
}
}
For production logs, shorten or redact the preview. The goal is to see whether the body looks like JSON, HTML, a login page, two JSON values, a log line, or a protocol stream. The goal is not to dump secrets.
Cause 5: A Framed JSON Stream Was Read Incorrectly
Some protocols send many JSON messages over one connection. JSON-RPC over stdio, the Language Server Protocol, and the Debug Adapter Protocol are common examples.
The stream may look roughly like this:
Content-Length: 83
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}Content-Length: 51
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}
That is not meant to be passed to JSON.parse() once. The Content-Length header is the frame. A client must:
- Read headers until the blank line.
- Read exactly the number of bytes in
Content-Length. - Parse that one JSON message.
- Loop for the next frame.
Do not split these streams by } or by newline. JSON strings can contain braces and escaped line breaks, and message bodies can be pretty-printed. The length header exists so the receiver does not have to guess.
How This Differs From "Unexpected End of JSON Input"
These two errors are easy to confuse because both appear when parsing fails, but they point in opposite directions.
| Error | What the parser saw | Usual cause |
|---|---|---|
Unexpected non-whitespace character after JSON |
A complete value, then extra data | Concatenated JSON, NDJSON, trailing logs, framed stream read as one blob |
Unexpected end of JSON input |
The input ended before the value was complete | Empty body, truncated response, missing ], missing }, unterminated string |
If the body is:
{"ok":true}{"debug":true}
the first object is complete, so the second { is extra data.
If the body is:
{"ok":true
the object never closes, so the parser reaches the end while still waiting for }.
Repair, Reject, or Reframe?
The right fix depends on where the data came from.
| Source | Safe action |
|---|---|
| Pasted snippet in a developer tool | Repair, then review the result |
| LLM output with a valid object plus commentary | Extract the JSON only if a human reviews it |
| NDJSON file | Parse line by line, or convert to an array intentionally |
| API response | Reject it and fix the server response |
| Webhook payload | Reject it; do not guess which event is canonical |
| Payment, permission, migration, or delete workflow | Reject malformed input and keep an audit trail |
| LSP, DAP, JSON-RPC, or another framed stream | Implement the framing protocol |
The dangerous shortcut is "just parse the first JSON value and ignore the rest." That may be acceptable in a clipboard utility, but it is a bad default for application code. Ignoring the rest can drop events, hide a proxy bug, or accept a tampered response with valid JSON at the front and junk after it.
A Practical Fix Checklist
- Capture the exact raw text that failed, or a redacted preview with length.
- Go to the reported position, line, or column.
- Identify the first extra character.
- Decide whether the input should be one JSON document, NDJSON, or a framed stream.
- Fix the producer if this came from an API, webhook, worker, or command used by other code.
- Add a regression test with the exact failing shape.
For concatenated objects, test the bad case:
expect(() => JSON.parse('{"id":1}{"id":2}')).toThrow(SyntaxError);
Then test the intended contract:
expect(JSON.parse('[{"id":1},{"id":2}]')).toEqual([
{ id: 1 },
{ id: 2 },
]);
For NDJSON, test the parser rather than the JSON grammar:
expect(parseNdjson('{"id":1}\n{"id":2}\n')).toEqual([
{ id: 1 },
{ id: 2 },
]);
Can JSON Fix Help?
JSON Fix is useful when you have a pasted example and want to see where the extra text begins. It can help clean up common almost-JSON mistakes and show you a formatted result in the browser.
For this particular error, review the repair carefully. A tool cannot know whether two objects should become an array, two NDJSON records, two protocol frames, or one object with an accidental suffix. That decision belongs to the data contract.
Use JSON Validator when you only need a strict yes/no parse check. Use JSON Viewer after the value parses and you want to inspect the shape. Use JSON Diff when you need to compare the repaired value with the original intent.
Frequently Asked Questions
What does "unexpected non-whitespace character after JSON data" mean?
It means the parser successfully read one complete JSON value, then found another non-whitespace character after it. A JSON document can have one top-level value only, so the extra character belongs to invalid trailing content, a second JSON value, or a stream framing problem.
What causes this JSON.parse error?
The most common causes are two JSON objects stuck together, NDJSON or JSON Lines parsed as one blob, a semicolon after JSON, debug text appended to an API response, a copied HTML wrapper, or a framed protocol stream read as plain JSON.
How is it different from "Unexpected end of JSON input"?
They point in opposite directions. Unexpected non-whitespace character after JSON means the parser had enough data to finish a value and then found extra data. Unexpected end of JSON input means the parser ran out of data before the value was complete.
How do I fix two JSON objects stuck together?
If they are meant to be one list, wrap them in a JSON array and separate them with a comma. If they are separate events or messages, keep them separate and parse each message through the correct delimiter or framing protocol.
How do I parse NDJSON or JSON Lines?
Split the input into lines, ignore blank lines only if your format allows that, and call JSON.parse() on each line. Do not call JSON.parse() once on the entire file unless the file is a single JSON array or object.
Should I just slice off everything after the first JSON value?
Usually no. Slicing can hide data loss, dropped events, proxy bugs, or tampered payloads. It is reasonable only for reviewed clipboard cleanup or a developer utility where the user can see exactly what was removed.
Can JSON Fix repair this error?
It can help locate and clean up extra text in a pasted sample, but it cannot know the correct data contract. Two objects may need an array, NDJSON parsing, stream framing, or a server-side fix.
How do I prevent this in APIs, logs, and LLM output?
Emit one JSON document per API response, keep diagnostics out of the body, use serializers instead of string concatenation, document NDJSON when using line-delimited records, and validate model output before handing it to application code.
Related Guides
- JSON Fix - repair and format pasted JSON locally in your browser.
- JSON Validator - check strict JSON syntax and parser positions.
- JSON Viewer - inspect parsed JSON as a collapsible tree.
- JSON Diff - compare before and after when repair matters.
- Unexpected End of JSON Input - the opposite "not enough data" parse error.
- How to Fix JSON.parse Unexpected Token Errors - the broader token-error guide.
- How to Handle Broken JSON in JavaScript - production handling patterns.
Sources
- RFC 8259 - the JSON Data Interchange Format.
- MDN JSON.parse - JavaScript parser behavior and exceptions.
- NDJSON specification - one JSON text per line for streaming records.
- JSON Lines - line-delimited JSON conventions.
- Language Server Protocol base protocol - an example of Content-Length framed JSON messages.
Last reviewed July 2026.