Common causes
- The API returned an HTML error page, 404 page or login redirect instead of JSON
- A PHP warning or notice was printed before the JSON output
- Calling JSON.parse on something that is already an object, which becomes "[object Object]"
- Single quotes, unquoted keys, trailing commas or comments, none of which are valid JSON
- A UTF-8 byte order mark (BOM) or stray whitespace character at the start of the file
- Parsing the string "undefined" from an empty localStorage value or variable
How to fix it
- Look at the raw response. Open DevTools > Network, click the request and view the Response tab, or log await res.text() before parsing. If it starts with <!DOCTYPE or <br />, the server sent HTML.
- Check the status and URL. Check res.ok and res.status before calling res.json(). A 404 or 500 usually returns HTML; fix the endpoint path or the server error first.
- Fix PHP output before the JSON. Look for 'Warning:' or 'Deprecated:' text in front of the JSON. Fix the warning, set display_errors=Off in production, and send header('Content-Type: application/json') followed by echo json_encode($data).
- Do not parse objects twice. If the value is already an object (for example from res.json() or axios response.data), use it directly. Unexpected token 'o' with "[object Object]" means it was parsed twice.
- Correct the JSON syntax. Use double quotes for all keys and strings, remove trailing commas and comments, and save the file as UTF-8 without BOM. A validator shows the exact line and column.
JavaScript
const res = await fetch('/api/items');
const text = await res.text();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
let data;
try {
data = JSON.parse(text);
} catch (e) {
console.error('Not JSON:', text.slice(0, 200));
throw e;
} How to stop it happening again
- Return JSON error bodies with the right status code from your API, not HTML pages
- Turn off display_errors in production and log PHP errors instead
- Generate JSON with JSON.stringify or json_encode rather than building strings by hand
- Check response.ok and the Content-Type header before parsing