Why it happens
Safari, Bun and other JavaScriptCore runtimes throw JSON Parse error: Unrecognized token '<' when the text given to JSON.parse starts with a character no JSON value can start with. The token in quotes tells you what you received:
'<': an HTML page or XML.' ', which looks like a space: a no-break space (U+00A0) copied from a web page or a chat app.'', with nothing visible between the quotes: a byte order mark (U+FEFF) at the start of the text.'/': a//or/*comment.'#': a#comment or a line of shell or YAML.'\': JSON escaped twice, such as{\"id\": 7}.'“': curly quotes from a word processor.
For '<', the server sent a web page: a 404 for a wrong URL (the Apache page in the checker above), a 5xx page from the app or a proxy, or a login page. Unexpected token ‘<’, “<!DOCTYPE “… is not valid JSON goes through each of these; the fixes are the same in every browser.
Related mistakes get their own JavaScriptCore messages: Unexpected identifier "undefined", Single quotes (') are not allowed in JSON, and Unexpected EOF for an empty string.
How to fix JSON Parse error: Unrecognized token
See what Safari received
Safari’s fetch hides the token: await res.json() rejects with The string did not match the expected pattern. and no position. Read the body as text and parse it yourself, so a failure logs what arrived:
async function getJson(url, options) {
const res = await fetch(url, options);
const text = await res.text();
try {
return JSON.parse(text);
} catch (err) {
console.error(`${res.status} ${res.url} ${res.headers.get('content-type')}: ${text.slice(0, 120)}`);
throw err;
}
}
On a Mac, the Network tab of Web Inspector (Option + Cmd + I) shows the same response.
React Native
React Native has used Hermes by default since 0.70, and Hermes words it JSON Parse error: Unexpected character: <. Apps still on JavaScriptCore print Unrecognized token '<'. In both, res.json() reads the body as text and passes it to JSON.parse, so the helper above works unchanged. Log res.url to see which host the build really called.
Bun
Bun runs on JavaScriptCore, so Bun scripts and CLI tools built with Bun print the same message. Unlike Safari, Bun’s res.json() keeps the token in its error.
Other tokens
If the token looks empty or like a space, paste the text into the JSON validator, which names invisible characters. To drop a leading byte order mark in code:
const data = JSON.parse(text.replace(/^\uFEFF/, ''));
For '/', remove the comments or use a parser that accepts them; Comments are not permitted in JSON covers the options. For '\', the producer serialized the data twice: serialize once.
Same error in other engines
- Firefox: JSON.parse: unexpected character at line 1 column 1 of the JSON data.
- Python: JSONDecodeError: Expecting value: line 1 column 1 (char 0).
All engines side by side: common JSON errors.