← 記事一覧

JSON の位置 1 で Unexpected Token o: 原因と修正

その小文字の "o" は "[object Object]" の 2 文字目。JSON.parse() に文字列ではなく JavaScript オブジェクトを渡しています。この間違いのあらゆる種類と、それぞれの一行修正。

SyntaxError: Unexpected token o in JSON at position 1 —— その小文字の o[object Object] の 2 文字目です。これは JavaScript オブジェクトを JSON 文字列ではなく直接 JSON.parse() に渡してしまったことを意味します。本記事ではこのエラーのあらゆるバリエーションと、1 分以内にそれぞれを修正する方法を解説します。

なぜエラーが「token o」と言うのか

文字列ではない値を JSON.parse() に渡すと、JavaScript はまずそれを文字列に強制変換します。プレーンなオブジェクトは "[object Object]" になります。パーサは位置 0 の [(有効 —— 配列の開始)を読み取り、次に位置 1 の o(無効 —— 有効な配列要素ではない)に出会います。だから:Unexpected token o at position 1

JSON.parse({})
// 同じこと: JSON.parse("[object Object]")
// SyntaxError: Unexpected token o in JSON at position 1

最も一般的な原因:response.json() を await していない

実際のコードベースでこのエラーが起きる最大の原因です。fetch()Response オブジェクトは文字列ではなく、ReadableStream のラッパーです。これを JSON.parse() に渡すと "[object Response]" に強制変換されます。

// ❌ 誤り —— response は Response オブジェクトであり文字列ではない
const response = await fetch('/api/user');
const data = JSON.parse(response);
// SyntaxError: Unexpected token o in JSON at position 1

// ✓ 正しい —— ブラウザにストリームを JSON としてパースさせる
const data = await response.json();

同様に、response.text()await なしで呼ぶと、Promise オブジェクトが返ります(これは "[object Promise]" に強制変換されます)。

// ❌ await 漏れ —— text は Promise であり文字列ではない
const text = response.text();
const data = JSON.parse(text);
// SyntaxError: Unexpected token o in JSON at position 1

// ✓ 正しい
const text = await response.text();
const data = JSON.parse(text);

このエラーを引き起こす他のオブジェクト

渡したものJS が強制変換する文字列エラーのトークン
{} または任意のプレーンオブジェクト"[object Object]"位置 1 の token o
undefined"undefined"位置 0 の token u
true / false"true" / "false" —— 実は有効な JSON!エラーなし
Response オブジェクト"[object Response]"位置 1 の token o
Promise"[object Promise]"位置 1 の token o
null"null" —— 有効な JSONエラーなし(null を返す)

原因:シリアライズせずにオブジェクトを渡している

関連する間違い:すでにメモリ上にある JavaScript オブジェクトを「パース」しようとすること。JSON.parse()文字列 を受け取ります。すでにオブジェクトを持っているなら、パースする必要はまったくありません。

const config = { host: 'localhost', port: 3000 };

// ❌ 無意味 —— config はすでにオブジェクト
const parsed = JSON.parse(config);

// ✓ ディープクローンが欲しいなら structuredClone か stringify + parse
const clone = JSON.parse(JSON.stringify(config));

// ✓ 送信したいなら JSON.stringify
const body = JSON.stringify(config);

原因:Python など他のバックエンドが間違った Content-Type を返している

サーバが "ok""not found" のようなプレーンテキストのエラーメッセージを、200 ステータスかつ Content-Type ヘッダなしで返してくることがあります。フロントエンドは JSON と仮定して response.json() を呼び、ボディが有効な JSON ではないため unexpected token エラーが発生します。

// サーバが送るリテラル文字列: ok
// ブラウザがそれを JSON としてパースしようとする:
JSON.parse("ok")
// SyntaxError: Unexpected token o in JSON at position 0

修正: response.json() を呼ぶ前に必ず response.okresponse.headers.get('Content-Type') をチェックします:

const response = await fetch('/api/action');
if (!response.ok) {
  const text = await response.text();   // 安全 —— JSON でない可能性がある
  throw new Error(`HTTP ${response.status}: ${text}`);
}
const data = await response.json();

あまり見ない [object X] のバリエーション

toString()[object で始まる文字列を返す値はすべて、位置 1 で同じエラーを起こします。[object Object] 以外で、実際のコードで遭遇するものは次のとおりです:

  • [object Module] —— 動的インポートの結果を渡している場合:const mod = await import('./data.json', { with: { type: 'json' } })。欲しいのは mod.default であって mod ではありません。
  • [object AsyncFunction] —— 非同期関数の参照を渡しており、呼び出して いない場合: fn ではなく fn()(Promise の解決値)を文字列化します。
  • [object HTMLDocument] —— document を誤って渡している(例: 古いデバッガのスニペットから)。
  • [object FormData] —— fetch(url, { body: formData }) は問題ありませんが、 JSON.parse(formData) はダメです。まず Object.fromEntries(formData) で変換してください。

クイック修正チェックリスト

  • fetch + JSON.parse? await response.json() に置き換えましょう。
  • 途中で await が漏れていないか? パースの前にある async な呼び出しはすべて await が必要です。
  • すでにオブジェクト? パースは不要です。そのまま使うか、structuredClone() でクローンしてください。
  • エラー時にサーバが JSON 以外を返す? 応答が JSON でない可能性があるときは response.text() で読み取り、response.json() は使わないでください。

よくある質問

「Unexpected token o in JSON at position 1」とはどういう意味?

JavaScript オブジェクトを JSON.parse() に渡しています。JavaScript はオブジェクトを文字列 "[object Object]" に強制変換し、パーサは位置 0 の [ は受け入れますが、位置 1 の o を拒否します。

fetch ではどう修正する?

JSON.parse(response) を呼ばないこと。代わりに await response.json() を使い、レスポンスストリームを読み取って自動的にパースします。チェーンの各ステップが await されていることを確認してください。

すでにオブジェクトがあるなら JSON.parse は必要?

いいえ。JSON.parse() は JSON 文字列 を値に変換するだけです。すでにオブジェクトを持っているならそのまま使うか、JSON.parse(JSON.stringify(obj)) の代わりに structuredClone() でディープクローンしてください。

「Unexpected token o」と「[object Object] is not valid JSON」は同じもの?

根本原因は同じ —— パース前にオブジェクトが文字列化されたことです。新しい V8 ビルドは [object Object] is not valid JSON という文言で出力し、古いビルドは Unexpected token o と出力します。詳細は 「[object Object] is not valid JSON」を修正する を参照してください。

生のレスポンスを確認する

API が実際に何を返しているか不明な場合は、生のボディを JSON Fix に貼り付けて検証してください。有効な JSON でなければ、何が間違っているかをバリデータが正確に教えてくれます。