JSON Lint / Unexpected token errors / "o" at position 1

Unexpected token o in JSON at position 1

This means you passed an object to JSON.parse and JavaScript coerced it to the string [object Object] before parsing. The parser sees [ at position 0 (looks like the start of a JSON array), then a stray o at position 1, and gives up. The fix is almost always replacing String(value) or implicit coercion with JSON.stringify(value).

Open the linter →

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

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.

Paste your JSON to find the error position →