SyntaxError: Unexpected token o in JSON at position 1 usually means JSON.parse() received the string "[object Object]" or another "[object ...]" value instead of JSON text. The lowercase o is the o in object. JavaScript coerced a non-string value to a string before the parser ran.
The fix is not to repair the JSON. The original data may still be sitting in memory as an object, or it may already have been flattened into the useless string [object Object]. First identify what you passed to JSON.parse(), then either use the object directly or serialize it deliberately with JSON.stringify().
Quick Diagnosis
Log the type and a safe string preview immediately before the failing parse:
function inspectParseInput(value) {
const text = typeof value === "string" ? value : String(value);
return {
type: typeof value,
constructor: value?.constructor?.name,
preview: JSON.stringify(text.slice(0, 120))
};
}
console.log(inspectParseInput(input));
Then match the result:
| What you see | What probably happened | Fix |
|---|---|---|
type: "object", constructor: "Object" |
You already have a JavaScript object | Do not parse it. Use it directly. |
preview: "[object Object]" |
An object was coerced into a string earlier | Find the coercion point and use JSON.stringify() there. |
constructor: "Response" |
You passed a fetch Response object |
Use await response.json() or await response.text() first. |
constructor: "Promise" |
You forgot await |
Await the promise before parsing. |
constructor: "FormData" |
You passed form data to a JSON parser | Use Object.fromEntries() or send multipart data as FormData. |
preview: "ok" and position is 0 |
The server returned plain text, not [object Object] |
Check content-type and parse only JSON responses. |
preview: "<!DOCTYPE html" |
HTML response, different error family | Debug Unexpected token <. |
type: "undefined" |
Missing value, different error family | Debug Unexpected token u. |
That one log line usually tells you whether to remove a parse, add an await, fix a fetch helper, or repair a bad storage/write path.
Why the Error Says Position 1
JSON.parse() coerces non-string inputs to strings before parsing. A plain object becomes:
const user = { id: 42, name: "Ada" };
String(user);
// "[object Object]"
Then the parser reads the string:
[object Object]
^
position 0: [ is a valid way to start a JSON array
^
position 1: o is not valid inside a JSON array
So the reported token is o at position 1, not [ at position 0.
The same pattern happens with other objects:
String(new Response("{}")); // "[object Response]"
String(Promise.resolve("{}")); // "[object Promise]"
String(new FormData()); // "[object FormData]"
The exact object name changes, but the parser still reaches the o in [object ...] and fails.
Cause 1: Parsing an Object You Already Have
This is the cleanest version of the bug:
const settings = {
theme: "dark",
compact: true
};
const parsed = JSON.parse(settings);
settings is already a JavaScript object. JSON.parse() is only for JSON text.
Use the value directly:
const settings = {
theme: "dark",
compact: true
};
console.log(settings.theme);
If you wanted a deep copy, use structuredClone() when the value is cloneable:
const copy = structuredClone(settings);
If you wanted JSON text, serialize:
const json = JSON.stringify(settings);
Those are three different operations: use, clone, serialize. JSON.parse(object) is none of them.
Cause 2: Calling JSON.parse(response) With fetch
A fetch Response object is a wrapper around status, headers, and a body stream. It is not the body text.
const response = await fetch("/api/user");
const data = JSON.parse(response);
That tries to parse "[object Response]".
Use response.json() when the endpoint is known to return JSON:
const response = await fetch("/api/user");
const data = await response.json();
When the endpoint may return HTML, plain text, or an empty body on errors, inspect first:
async function readJsonResponse(response) {
const text = await response.text();
const contentType = response.headers.get("content-type") || "";
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${text.slice(0, 180)}`);
}
if (!contentType.toLowerCase().includes("application/json")) {
throw new Error(`Expected JSON, got ${contentType || "no content-type"}`);
}
return JSON.parse(text);
}
That helper also protects you from the sibling error Unexpected token <, where the response body is an HTML page.
Cause 3: Forgetting await
Promises stringify to "[object Promise]", which produces the same token position.
const response = await fetch("/api/user");
const text = response.text();
const data = JSON.parse(text);
response.text() returns a promise. Add await:
const response = await fetch("/api/user");
const text = await response.text();
const data = JSON.parse(text);
The same mistake appears with helper functions:
async function loadSettingsText() {
return "{\"theme\":\"dark\"}";
}
const raw = loadSettingsText();
const settings = JSON.parse(raw);
Fix:
const raw = await loadSettingsText();
const settings = JSON.parse(raw);
If your debug log says constructor: "Promise", do not change the JSON. Change the async flow.
Cause 4: Parsing the Result of response.json() Again
response.json() already parses the body. The result is a JavaScript value.
const response = await fetch("/api/user");
const data = await response.json();
const parsedAgain = JSON.parse(data);
If data is an object, the second parse causes the o error.
Use the parsed value:
const response = await fetch("/api/user");
const data = await response.json();
renderUser(data);
This also happens with API clients that parse JSON for you. For example, Axios usually puts the parsed response body in response.data when the server returns JSON:
const response = await axios.get("/api/user");
const user = response.data;
// Do not JSON.parse(user) if it is already an object.
The rule is simple: parse at the boundary once. After that, pass typed values through your app.
Cause 5: Storing Objects in localStorage Without JSON.stringify()
Web storage stores strings. If you store an object directly, it becomes [object Object].
const draft = { title: "Incident notes", tags: ["api", "json"] };
localStorage.setItem("draft", draft);
Later:
const raw = localStorage.getItem("draft");
const draft = JSON.parse(raw);
If raw is "[object Object]", the original object shape is gone. Fix the write path:
const draft = { title: "Incident notes", tags: ["api", "json"] };
localStorage.setItem("draft", JSON.stringify(draft));
const raw = localStorage.getItem("draft");
const savedDraft = raw ? JSON.parse(raw) : null;
When debugging storage bugs, inspect the actual stored value in DevTools Application or Storage panel. The failing parse may happen on page load, but the bad write happened earlier.
Cause 6: Sending or Receiving Request Bodies Incorrectly
If an API expects JSON, send JSON text:
const payload = { email: "ada@example.com", plan: "pro" };
await fetch("/api/account", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(payload)
});
Do not rely on implicit object-to-string conversion. Native fetch expects a valid body type, and wrappers differ in how they handle plain objects. If your server logs show the literal text [object Object], the client or middleware coerced the object before it reached the parser.
On the server, log the raw body safely when debugging. If the raw body is [object Object], a JSON repair tool cannot reconstruct the missing fields. Fix the producer.
Cause 7: FormData, URLSearchParams, and Query Strings
FormData and URLSearchParams are useful, but they are not JSON text.
const formData = new FormData(form);
JSON.parse(formData);
If the API expects JSON, convert form fields into a plain object and stringify:
const formData = new FormData(form);
const body = Object.fromEntries(formData.entries());
await fetch("/api/profile", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body)
});
If the API expects file uploads, send FormData as FormData. Do not stringify it and do not parse it.
Query strings have the same issue with nested objects:
const filters = { status: "open", owner: "me" };
const params = new URLSearchParams({ filters });
String(params);
// "filters=%5Bobject+Object%5D"
Flatten the query or encode the nested object intentionally:
const params = new URLSearchParams({
filters: JSON.stringify(filters)
});
Token o at Position 0 Is Different
Sometimes the message says token o at position 0:
JSON.parse("ok");
That is not [object Object]. It means the input text literally starts with o, often because an API returned "ok", "offline", or "object" as plain text.
Fix that by checking status, content-type, and raw body before parsing:
const response = await fetch("/api/action");
const text = await response.text();
if (!response.headers.get("content-type")?.includes("application/json")) {
throw new Error(`Expected JSON, got: ${text.slice(0, 80)}`);
}
const data = JSON.parse(text);
Position matters. Position 1 usually points to [object ...]; position 0 usually points to plain text or another non-JSON literal.
A Practical Safe Parser
At app boundaries, it is useful to make the type decision explicit:
function parseJsonIfString(value, fallback = null) {
if (typeof value === "string") {
if (!value.trim()) return fallback;
return JSON.parse(value);
}
if (value !== null && typeof value === "object") {
return value;
}
return fallback;
}
Use a helper like this only at messy boundaries: storage reads, clipboard input, query params, or third-party integrations. Inside your own code, prefer stricter contracts. A function should usually accept either a JSON string or an object, not both.
Can JSON Fix Recover It?
If the input is literally [object Object], no tool can recover the original keys and values. The data was lost when the object was coerced to a string.
JSON Fix can still help you inspect a suspicious value:
- If the value is valid JSON text, it will format it.
- If the value is almost JSON, it may repair it.
- If the value is
[object Object], the fix is in your code, not the text.
For the broader [object Object] family, see Fix "[object Object] is not valid JSON".
Frequently Asked Questions
What does "Unexpected token o in JSON at position 1" mean?
It usually means a JavaScript object, Response, Promise, FormData, or another object was coerced to a string like [object Object], then passed to JSON.parse(). The parser accepts [ at position 0 and rejects the o in object at position 1.
How do I fix it with fetch?
Do not call JSON.parse(response). A fetch Response is not the body text. Use await response.json() for known JSON responses, or read await response.text(), check status and content-type, then call JSON.parse(text).
Why do I get this after response.json()?
response.json() already parsed the body. If you call JSON.parse(data) after const data = await response.json(), you are parsing an object again. Use data directly.
Do I need JSON.parse() if I already have an object?
No. JSON.parse() converts JSON text into a JavaScript value. If you already have an object, use it directly, clone it with structuredClone() when needed, or serialize it with JSON.stringify() when you need JSON text.
Is "Unexpected token o" the same as "[object Object] is not valid JSON"?
They are the same family of bug. Newer runtimes may show [object Object] is not valid JSON; older V8 messages often show Unexpected token o in JSON at position 1.
What if the token o is at position 0?
That usually means the input text starts with o, such as ok or another plain-text response. It is not the [object Object] pattern, so check the raw response body and content-type.
Can JSON Fix repair [object Object]?
No. Once an object has become the string [object Object], the original fields are gone. JSON Fix can confirm it is not JSON, but you need to fix the code that coerced the object.
How do I prevent this in production?
Parse only strings, serialize request/storage values with JSON.stringify(), do not parse values already returned by response.json() or Axios, await promises before parsing, and log type plus a short preview before handling parse errors.
Related Tools & Guides
- JSON Fix - check whether a value is JSON, almost JSON, or not JSON at all
- Fix "[object Object] is not valid JSON" - the broader object-coercion guide
- Unexpected token u in JSON at position 0 -
undefinedwas parsed - Unexpected token < in JSON at position 0 - HTML was parsed
- Unexpected end of JSON input - empty or truncated JSON
- JSON.parse Unexpected Token errors - token-by-token overview
- Handling broken JSON in JavaScript - safe parsing and repair patterns
Sources
- MDN: JSON.parse() - JavaScript JSON parser behavior
- ECMA-262 JSON.parse algorithm - coercion before parsing
- MDN: Object.prototype.toString() - default
[object Type]string form - MDN: Response.json() - fetch response parsing
- MDN: structuredClone() - cloning JavaScript values without stringify/parse
Last reviewed July 2026.