How to Fix JSON.parse SyntaxError: Bad Parsing
Find why JSON.parse throws a SyntaxError, fix malformed JSON, and diagnose responses that contain something other than JSON.
JSON.parse() throws a SyntaxError when the string you give it does not conform to JSON grammar. Check the reported position and the punctuation just before it, then look for single quotes, trailing commas, invalid numbers or literals, unfinished strings or structures, or extra text after the JSON value. If the input came from an HTTP response, inspect its status and raw body first: the server may have returned HTML, plain text, or an empty body instead of JSON.
JSON looks similar to a JavaScript object literal, but it is stricter. Use double quotes around JSON strings and property names, and use JSON.stringify() to serialize JavaScript values rather than assembling JSON punctuation by hand. [MDN: JSON.parse bad parsing] [RFC 8259]
1. Read the error and inspect the input
JavaScript engines phrase JSON parsing errors differently. One runtime may report a line and column; another may describe an unexpected token or the end of the input. Treat the location as a clue, not a guaranteed pointer to the original mistake. A missing quote or comma earlier in the text can make a later character look invalid.
- Keep the exact string that failed, including whitespace and line breaks.
- Inspect the reported character and the syntax immediately before it.
- Check whether the input ends in the middle of a string, escape sequence, number, array, or object.
- Confirm that the input contains one complete JSON value and no extra non-whitespace characters afterward.
For network data, inspect the response status and body in the browser Network panel or log the raw text before parsing. This separates invalid JSON from a response that was never JSON.
2. Fix common JSON syntax errors
| Problem | Invalid input | Correction |
|---|---|---|
| Single-quoted strings or property names | {'name': 'Ada'} |
{"name": "Ada"} |
| Trailing comma | [1, 2,] or {"name":"Ada",} |
[1, 2] or {"name":"Ada"} |
| Leading zero in a number | 01 |
1 |
| Missing digits after decimal point or exponent | 1., 1e, 1e+ |
1.0, 1e0, 1e+2 |
| JavaScript-only values | undefined, NaN, Infinity |
Represent the value with a JSON-compatible value or a documented string/null convention. |
| Wrong literal capitalization | True, False, Null |
true, false, null |
| Unescaped quote, backslash, control character, or raw newline inside a string | A string containing an unescaped quote or line break | Escape quotation marks, reverse solidus characters, and control characters according to JSON string rules. |
| Incomplete or extra text | {"ok": true or {"ok":true} trailing |
Close all structures and leave only whitespace after the complete JSON value. |
JSON text represents a complete serialized value. It can be an object, array, number, string, boolean, or null; its literals are lowercase. JSON numbers do not allow leading zeros except for zero itself, and a decimal point or exponent must be followed by digits. [RFC 8259, JSON values] [RFC 8259, numbers]
3. Use a safe JavaScript parse pattern
Keep the source text available while diagnosing. Logging only the error often loses the evidence needed to identify a malformed payload.
const rawText = '{"name":"Ada","active":true}';
try {
const value = JSON.parse(rawText);
console.log("Parsed value:", value);
} catch (error) {
console.error("Could not parse JSON:", error);
console.error("Raw input:", rawText);
}
If the data begins as a JavaScript value, serialize it rather than hand-building JSON:
const payload = { name: "Ada", active: true };
const rawText = JSON.stringify(payload);
const copy = JSON.parse(rawText);
console.log(rawText);
console.log(copy.name);
Do not use eval() to work around a JSON syntax error. It executes JavaScript and does not validate input using JSON grammar. [MDN: JSON.parse()] [RFC 8259, parsers]
4. Diagnose JSON returned by fetch
Response.json() reads the response body and parses it as JSON. It rejects with a SyntaxError if the body cannot be parsed. Check the status and content type, and preserve the raw body when parsing fails. [MDN: Response.json()]
async function getJson(url) {
const response = await fetch(url);
const rawText = await response.text();
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${rawText.slice(0, 500)}`);
}
const contentType = response.headers.get("content-type") || "";
if (!contentType.toLowerCase().includes("application/json")) {
throw new Error(`Expected JSON but received ${contentType || "no content type"}: ${rawText.slice(0, 500)}`);
}
try {
return JSON.parse(rawText);
} catch (error) {
console.error("Invalid JSON response body:", rawText);
throw error;
}
}
This diagnostic reads the body as text so it can log it and then parse it. A response body can only be consumed once, so do not call response.text() and then response.json() on the same response unless you clone it first. If you do not need the raw text, the direct version is simply:
const response = await fetch("/api/data");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
Check the endpoint contract before requiring a particular content type: some APIs use a different JSON media type. Also account for endpoints that legitimately return no body, such as a successful response with no content; do not try to parse an empty body as JSON.
5. Troubleshoot by symptom
| Symptom | Likely cause | What to do |
|---|---|---|
| Unexpected end of JSON input | Empty or truncated response, or an unclosed string, array, or object. | Log the exact raw body and check whether the server or transport returned the complete payload. Close the missing structure at the producer. |
| Unexpected token < or an error at the first character | Often an HTML page, such as an error page, was returned instead of JSON. | Check HTTP status, redirects, endpoint URL, authentication, and raw response body. |
| Error near a comma or closing bracket | Trailing comma, missing separator, or missing value. | Inspect the preceding member/value and remove the extra comma or add the missing JSON value. |
| Error near a quote or backslash | Unterminated string or invalid escape. | Check string boundaries and escape quotes, backslashes, and control characters. |
| Valid JSON-looking value still fails | The string may contain extra text, be truncated, or differ from what was inspected. | Log the exact string with a representation that makes newlines and control characters visible; parse that exact value. |
| Parsing works once but fails after refactoring | The code may parse an already parsed object a second time. | Check the type and value at each boundary. response.json() already returns parsed data; pass that object onward without JSON.parse(). |
Do not assume the parser’s reported position is where the source data first went wrong. Verify the exact input rather than changing a nearby character blindly.
6. cURL, Python, and Node.js checks for a JSON endpoint
These examples retrieve an endpoint and inspect the response before treating it as JSON. Replace the example URL with an endpoint that is expected to return JSON.
cURL
curl --fail-with-body --silent --show-error \
--header 'Accept: application/json' \
--dump-header response-headers.txt \
--output response-body.txt \
'https://example.com/api/data'
cat response-headers.txt
cat response-body.txt
Review the status and body. If the body is JSON, you can validate its syntax with a local JSON-aware tool such as Python’s standard library:
python -m json.tool response-body.txt
Python
import json
import requests
url = "https://example.com/api/data"
response = requests.get(url, headers={"Accept": "application/json"}, timeout=30)
print("HTTP status:", response.status_code)
print("Content-Type:", response.headers.get("content-type"))
print("Raw body:", response.text[:1000])
response.raise_for_status()
data = response.json()
print(data)
requests.Response.json() raises a decoding error if the body is not valid JSON. Inspect response.text and the status before treating that as a syntax defect in your own data.
Node.js
const response = await fetch("https://example.com/api/data", {
headers: { Accept: "application/json" }
});
const rawText = await response.text();
console.log("HTTP status:", response.status);
console.log("Content-Type:", response.headers.get("content-type"));
console.log("Raw body:", rawText.slice(0, 1000));
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${rawText.slice(0, 500)}`);
}
const data = JSON.parse(rawText);
console.log(data);
For large or sensitive payloads, avoid dumping the entire body into shared logs. Keep a safe local sample or redact secrets while preserving the punctuation and characters needed to reproduce the parse error.
7. Performance, reliability, and cost
- Performance: Parsing adds work proportional to the amount of input. Avoid parsing the same text repeatedly; parse once at the boundary and pass the resulting value through the application.
- Reliability: Validate status and the endpoint’s expected response shape. A syntactically valid JSON value can still have missing fields or unexpected types, so parsing alone is not schema validation.
- Retries: Retrying a deterministic malformed payload usually repeats the failure. Retry only when the request or service behavior is plausibly transient, and preserve the original response for diagnosis.
- Cost: JSON.parse itself has no service charge. Network calls, logging volume, and any paid service used to obtain or inspect data may have their own costs; avoid repeated requests when a captured response is sufficient.
8. Capture the page behind a response with ScreenshotNeo
If the failing body appears to be an HTML error page or a page with a consent banner, screenshot the page to inspect what a visitor sees. ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. Its API can return an image or PDF from one GET request. [ScreenshotNeo API documentation]
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write("shot.webp", res);
ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. It reports page verdict and billing state in response headers, and bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. See all options in the docs.
Or skip the browser setup
Cookie banners, popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed; an MCP server lets AI agents take screenshots; 1,000 screenshots a month are free with no card, and paid plans start at $5 for 3,000. Create a free ScreenshotNeo account to get started.
FAQ
Is every JavaScript object a valid JSON value?
No. JavaScript object literals can use syntax JSON forbids, such as unquoted property names, single-quoted strings, and trailing commas. Serialize values with JSON.stringify() when producing JSON.
Can JSON contain comments?
No. Comments are not part of JSON grammar. Remove comments or use a format that explicitly supports them when comments are required.
Can a JSON response be valid but still break my application?
Yes. Parsing checks syntax, not whether the resulting value has the fields, types, or meaning your application expects. Validate the parsed value against the endpoint’s contract.


