The minimal reproduction
JSON.parse({a: 1});
// SyntaxError: Unexpected token o in JSON at position 1
Same error appears any time an object reaches JSON.parse
without going through JSON.stringify first.
The four places it leaks in
1. fetch body without stringify
// Wrong
fetch("/api/user", {
method: "POST",
body: {name: "Eli"}, // ← coerced to "[object Object]"
});
// Right
fetch("/api/user", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({name: "Eli"}),
});
The server tries JSON.parse(body) on the request body
and explodes with this exact error. Always stringify before sending.
2. localStorage / sessionStorage
// Wrong
localStorage.setItem("user", {name: "Eli"}); // stored as "[object Object]"
const u = JSON.parse(localStorage.getItem("user")); // ← error
// Right
localStorage.setItem("user", JSON.stringify({name: "Eli"}));
const u = JSON.parse(localStorage.getItem("user")); Storage APIs only hold strings — they implicitly coerce anything else. Forgetting to stringify on write is a one-line bug that surfaces as a parse error on read.
3. URL search params or cookies
// Wrong
url.searchParams.set("filter", {status: "open"}); // "[object Object]"
// Right
url.searchParams.set("filter", JSON.stringify({status: "open"})); Same pattern: the API expects a string and silently coerces. Cookies and querystrings produce the same trap.
4. Logging a raw object then re-parsing the log line
// Wrong (in a pipeline that later parses log lines as JSON)
console.log("user=" + userObject); // "user=[object Object]"
// Right
console.log("user=" + JSON.stringify(userObject)); Common in services that ship logs to Splunk or Datadog and parse structured fields. The downstream parser hits the same error.
One-rule prevention
Never use +, template literals, or
String() on a value that might be an object if the
result will be parsed later. Always go through
JSON.stringify. TypeScript catches almost every case
at the boundary; ESLint's no-implicit-coercion
plus no-string-concat-on-objects rules cover the rest.
Related errors
- "Unexpected token u in JSON at position 0" —
undefinedpassed to parse - "Unexpected token <" and other variants — full error guide
- Why JSON rejects comments and trailing commas
FAQ
Why specifically 'o' at position 1?
When JavaScript coerces an Object to a string (e.g., with String(obj) or concatenation), the result is the literal text '[object Object]'. JSON.parse sees [ at position 0 (the start of a valid JSON array — so far so good), then 'o' at position 1 where it expected ',' or ']' or a value, and throws.
What's the difference between String(obj) and JSON.stringify(obj)?
String(obj) calls obj.toString(), which by default returns '[object Object]' — useless for serialization. JSON.stringify(obj) walks the object's enumerable properties and produces real JSON. You almost always want stringify, not String.
Why does this happen when I post a fetch body?
fetch(url, { body: someObject }) doesn't auto-stringify — it calls String() on non-string bodies. You need body: JSON.stringify(someObject) and the matching Content-Type: application/json header. On the receiving side, parsing the body as JSON then succeeds.
Can React or Vue cause this?
Indirectly. If you pass an object where a string is expected (a prop type mismatch, or {error.message} when error is a string and you accidentally wrap it), and that string gets parsed later, the same '[object Object]' string appears. The error surfaces wherever JSON.parse runs, not where the bad coercion happened.
How do I prevent this for the whole codebase?
TypeScript catches most cases — JSON.parse's signature is (text: string) => any, so passing an object is a type error. In JS, an ESLint rule like no-implicit-coercion plus pre-stringifying with JSON.stringify(obj) makes the bug impossible.