Unexpected token in JSONとは?意味と直し方

このエラーは、解析しようとしたテキストがJSONではないことを意味します。エラーに出てくるトークンを見れば、代わりに何を受け取ったのかがたいてい分かります。

JSON.parse()やresponse.json()を呼んだのに、データではなくSyntaxErrorが返ってくる。文言はブラウザーによって違いますが、意味はいつも同じです。パーサーがテキストを先頭から読み進め、正しいJSONならその位置に現れるはずのない文字に出会った、ということです。エラーが引用している文字とその位置は、手元にある最も有力な手がかりです。

エンジンごとのエラーメッセージ

新しいバージョンのChromeとNode.jsでは文言が変わったため、検索結果や古いログでは両方の形を目にします。

表示される場所メッセージ
Chrome、Edge、Node.js 20以降Unexpected token '<', "<!DOCTYPE "... is not valid JSON
古いChromeとNode.js 18Unexpected token < in JSON at position 0
FirefoxJSON.parse: unexpected character at line 1 column 1 of the JSON data
SafariJSON Parse error: Unrecognized token '<'

「position 0」や「line 1 column 1」は、最初の1文字目からすでにおかしいという意味です。これはJSONドキュメント内の小さな打ち間違いではなく、レスポンスがそもそもJSON以外のものであることをほぼ確実に示しています。

よくある原因:JSONの代わりにHTMLページが返っている

位置0で予期しない<が出るケースが圧倒的に多いパターンです。コードはAPIにJSONを求めたのに、サーバーがHTMLページを返しており、HTMLは<!DOCTYPE html>や<html>で始まります。典型的な理由は次のとおりです。

  • URLの誤り:打ち間違いや/api接頭辞の付け忘れで、サイトの404ページが返っている。
  • サーバーエラー:フレームワークがHTMLの500エラーページを描画している。
  • セッション切れ:リクエストがログインページにリダイレクトされ、fetchがそのリダイレクトを黙って追いかけている。
  • シングルページアプリのフォールバック:開発サーバーやホスティングが、未知のパスに対してステータス200でindex.htmlを返している。
  • 途中にあるプロキシ、CDN、ファイアウォールが、独自のHTMLエラーページやチャレンジページを返している。

いちばん手早く確かめる方法は、パースする前に生のレスポンスを見ることです。

const res = await fetch('/api/items');
console.log(res.status, res.headers.get('content-type'));
const text = await res.text();
console.log(text.slice(0, 200)); // shows the HTML if that is what came back

ブラウザーなら、開発者ツールのNetworkパネルでリクエストをクリックし、Responseタブを確認する方法もあります。HTMLが見えたら、直すべきはURL、認証、またはサーバーエラーです。パース処理のコード自体には問題ありません。

その他のトークンと、それが示すもの

現在のメッセージ(旧メッセージ)パースしたもの対処法
"undefined" is not valid JSON(Unexpected token u in JSON at position 0)値undefined。たとえば存在しないlocalStorageのキーや、未設定の変数パースする前に値が存在するか確認する
"[object Object]" is not valid JSON(Unexpected token o in JSON at position 1)すでにパース済みのオブジェクトが、JSON.parseによって文字列に変換されたものオブジェクトをそのまま使い、二重にパースしない
Expected property name or '}' in JSON at position 1{'a': 1}や{a: 1}のような、シングルクォートやクォートなしのキーすべてのキーと文字列をダブルクォートで囲む
Expected double-quoted property name in JSON at position 8{"a": 1,}のような末尾のカンマ閉じ括弧の直前のカンマを削除する
Unexpected token 'N', "{"a": NaN}" is not valid JSONデータ内のNaN、Infinity、undefinednullまたは数値に置き換える
位置0で、見えない、または空に見えるトークンを引用するメッセージ一部のWindowsエディターで保存したファイルの先頭にあるバイトオーダーマーク(BOM)BOMなしのUTF-8で保存し直すか、先頭の1文字を取り除く

パース済みのデータをもう一度パースしている

ライブラリは、すでに組み立て済みのオブジェクトを渡してくることがよくあります。response.json()はすでにJavaScriptのオブジェクトを返しますし、axiosのresponse.dataも同様です。そのオブジェクトをJSON.parseに渡すと、まず文字列"[object Object]"に変換され、位置1で失敗します。余分なパースを削除してください。

const data = await res.json();
// JSON.parse(data)  <- fails: data is already an object
console.log(data.items);

本物のJSONの中にある間違いを探す

エラー位置が途中のどこかを指している場合、テキストは確かにJSONで、その中に間違いがあります。JSON検証ツールに貼り付けてください。ブラウザーと同じパーサーを使ってメッセージと位置・行を表示し、正しいJSONになった時点でドキュメントを整形します。実際の間違いは、報告された位置の少し手前にあることがほとんどです。たとえば2つの要素の間のカンマ抜けや、ダブルクォートであるべき引用符などです。

コード側で防ぐ

パースする前にステータスとContent-Typeを確認すれば、わかりにくい構文エラーを、実際に何が起きたのかを伝えるメッセージに変えられます。

async function getJson(url) {
  const res = await fetch(url);
  const type = res.headers.get('content-type') || '';
  if (!res.ok || !type.includes('application/json')) {
    const body = await res.text();
    throw new Error(`Expected JSON from ${url}, got ${res.status} ${type}: ${body.slice(0, 100)}`);
  }
  return res.json();
}

サーバー側でも考え方は同じです。APIのルートではHTMLのエラーページではなくJSONのエラーボディを返し、Content-Type: application/jsonを付けて送ってください。ボディが空、または途中で切れているときに出るよく似たメッセージについては、Unexpected end of JSON inputを参照してください。

よくある質問

「Unexpected token < in JSON at position 0」とはどういう意味ですか?

レスポンスが<という文字で始まっていた、つまりJSONではなくHTMLだったという意味です。多くはエラーページ、ログインページ、index.htmlへのフォールバックです。リクエストのURL、ステータスコード、Content-Typeヘッダーを確認してください。

「"[object Object]" is not valid JSON」と出るのはなぜですか?

JSON.parseにオブジェクトを渡しています。オブジェクトはまず[object Object]というテキストに変換されます。値はすでにパース済みなので、そのまま使ってください。

JSONバリデーターで問題を見つけられますか?

テキストがJSONであるはずなら、見つけられます。バリデーターは最初のエラーの正確な位置を示します。テキストがHTMLなら、直すべきはJSONではなくサーバー側かリクエストです。