← 記事一覧

JSON.parse "Unexpected Token" エラーの直し方

"Unexpected token '<'" や "Unexpected token u in JSON at position 0" —— これらはアプリを止める。各種の意味と、正確な直し方を解説。

赤いスタックトレースを眺めています:SyntaxError: Unexpected token '<', "..." is not valid JSON。あるいは Unexpected token u in JSON at position 0。どちらにせよ、JSON.parse() が実行を拒否し、コードが止まりました。本ガイドではこのエラーの各バリアントが何を意味し、なぜ起こり、どう速く直すかを正確に解説します。

エラーの実際の見た目

文言は JavaScript エンジンごとに異なりますが、本質は同じ:パーサが文字列の特定位置で予期しない文字に出会ったということ。

// V8 (Chrome, Node.js, Edge)
SyntaxError: Unexpected token '<', "<html>..." is not valid JSON
SyntaxError: Unexpected token 'u', "undefined" is not valid JSON
SyntaxError: Unexpected token u in JSON at position 0  // 古い V8
SyntaxError: Expected ',' or '}' after property value in JSON at position 42

// Firefox (SpiderMonkey)
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
SyntaxError: JSON.parse: unexpected non-whitespace character after JSON data

// Safari (JavaScriptCore)
SyntaxError: JSON Parse error: Unexpected identifier "undefined"
SyntaxError: JSON Parse error: Single quotes (') are not allowed in JSON

重要な情報は常に二つ:どんな文字が予期しなかったかどこで。文字はパーサが実際に受け取ったものを教えてくれ、位置は文字列のどこを見るかを教えてくれます。

原因 1:サーバが JSON ではなく HTML を返した

Unexpected token '<' の最も一般的な原因。fetch や AJAX 呼び出しが期待した JSON ではなく HTML ページ —— 通常は 404 ページ、ログインリダイレクト、サーバエラー —— を返しました。<<!DOCTYPE html><html> の開きタグ。

// ❌ これは "Unexpected token '<'" で落ちる
const res  = await fetch('/api/user');
const data = await res.json(); // サーバが HTML 404 を返した

// ✅ レスポンス status を先にチェック
const res = await fetch('/api/user');
if (!res.ok) {
  throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();

res.json() を呼ぶ前に常に res.ok(または res.status)を確認。生レスポンスをまず覗くこともできます:

const text = await res.text();
console.log(text.slice(0, 200)); // 実際に何を受け取ったか確認
const data = JSON.parse(text);

原因 2:値が undefined

Unexpected token 'u' が position 0 でほぼ常に JSON.parse(undefined) を呼んだことを意味します。文字列 "undefined" は文字 u から始まり、u は妥当な JSON 開始文字ではありません。

// ❌ 変数が一度も設定されていない
const raw = localStorage.getItem('settings'); // 欠落時は null を返す
JSON.parse(raw);  // null は OK だが、raw がもし undefined だと…

// ❌ async データがまだロードされていない
JSON.parse(this.state.data); // 初回 render では data は undefined

// ✅ パース前にガード
if (raw) {
  const data = JSON.parse(raw);
}

localStorage.getItem() は欠落キーに対して nullundefined ではない)を返し、JSON.parse(null) は問題なく null を返します。よくある犯人は未初期化の state や欠けた関数引数。

原因 3:二重引用符の代わりに一重引用符

// ❌ SyntaxError: Unexpected token '''
JSON.parse("{'name': 'Ada'}");

// ✅ JSON は二重引用符が必須
JSON.parse('{"name": "Ada"}')

JavaScript のオブジェクトリテラルは一重引用符を受け付けます。JSON はダメ —— キーを含む全文字列に二重引用符が必須。Python の str()json.dumps() の代わりに使ってシリアライズした場合や、オブジェクトリテラルをそのままコピーした場合によく起こります。

原因 4:末尾カンマ

// ❌ SyntaxError: Unexpected token '}'
JSON.parse('{"name": "Ada", "score": 98,}');
//                                       ^ 末尾カンマ

// ✅
JSON.parse('{"name": "Ada", "score": 98}')

モダン JavaScript は配列とオブジェクトの末尾カンマを許します。JSON は許しません。手書きの JSON 設定ファイルがこのエラーの最も一般的な源。

原因 5:引用符なしのキー

// ❌ SyntaxError: Unexpected token 'n'
JSON.parse("{name: 'Ada'}");

// ✅
JSON.parse('{"name": "Ada"}')

JSON のオブジェクトキーは常に二重引用符付き文字列でなければなりません。name のような裸の識別子は JavaScript のオブジェクトリテラルでは妥当ですが JSON では不可。

原因 6:JSON 内のコメント

// ❌ SyntaxError: Unexpected token '/'
JSON.parse(`{
  // ユーザレコード
  "name": "Ada"
}`);

// ✅ パース前にコメントを除去(または JSONC パーサを使う)
const stripped = raw.replace(///.*$/gm, '').replace(//*[sS]*?*//g, '');
JSON.parse(stripped);

JSON にはコメント構文がありません。コメントは設定ファイル(JSONC 形式)によく追加されますが、標準の JSON.parse() は即座に拒否します。

原因 7:position 0 の BOM や不可視文字

エラーが position 0 と言うのに最初の可視文字に問題が見えない場合、文字列先頭に不可視の byte-order mark(BOM、U+FEFF)や他の制御文字が付いている可能性 —— 典型的には Windows で UTF-8-BOM エンコーディング保存されたファイルを読むとき。

// ✅ パース前に BOM を除去
const clean = raw.replace(/^/, '');
JSON.parse(clean);

原因 8:トークン I または N —— InfinityNaN

エラーが Unexpected token I または Unexpected token N と言うなら、ソースが Infinity-InfinityNaN を出力しています —— どれも JavaScript では妥当ですが JSON では不可

// ❌ プロデューサ側の誤り
const json = '{"ratio": NaN, "limit": Infinity}';
JSON.parse(json); // Unexpected token N (or I) ...

// ✅ 明示的にエンコード
const safe = JSON.stringify({ ratio: NaN, limit: Infinity });
// → '{"ratio":null,"limit":null}'  (注意:ロスあり —— どちらも null になる)

// ✅ ロスレス:センチネル文字列としてエンコードし自分で解析
JSON.stringify({ ratio: 'NaN', limit: 'Infinity' });

JSON.stringifyNaN / Infinity を黙って null に変換します。ラウンドトリップが必要なら出力時にセンチネル文字列に置き換え、入力時に解析し直す。

エラーの正確な位置を見つける方法

エラーメッセージに position 番号が含まれます。それで文字列をスライスし、そこに何があるかを正確に見ます:

function parseWithContext(text) {
  try {
    return JSON.parse(text);
  } catch (err) {
    // V8 エラーメッセージから position を抽出
    const match = err.message.match(/position (\d+)/);
    if (match) {
      const pos = Number(match[1]);
      const snippet = text.slice(Math.max(0, pos - 20), pos + 20);
      console.error(`Error near: ..."${snippet}"...`);
      console.error(`             ${''.padStart(Math.min(pos, 20), ' ')}^`);
    }
    throw err;
  }
}

よくある質問

「Unexpected token in JSON」は何を意味する?

JSON.parse() が文法のその時点で妥当でない文字に当たったということ。名指しされたトークンが受け取ったもの(< = HTML、u = undefined' = 一重引用符)を教え、位置はどこを見るべきかを教える。

fetch で「Unexpected token '<'」が起きるのはなぜ?

リクエストが JSON ではなく HTML ページ —— 404、ログインリダイレクト、サーバエラー —— を返し、< は開きの HTML タグ。response.json() を呼ぶ前に response.ok を確認し、await response.text() をログして実際に何が返ったかを見る。

「Unexpected token u in JSON at position 0」をどう直す?

undefinedJSON.parse() に渡した。先に値をガード(if (raw) JSON.parse(raw))し、未初期化 state や欠落した関数引数を確認。近縁の token o バリアントは Unexpected token o in JSON で。

エラーの正確な位置をどう見つける?

エラーメッセージから position 番号を読み取り、その周辺で文字列をスライス(上のスニペット参照)、または JSON を JSON Fix に貼ると問題行が強調されます。

最速の修正:オンラインで貼って修復

壊れた JSON 文字列を今すぐ直したいなら、JSON Fix が上記の原因すべてを自動的に処理 —— 一重引用符、末尾カンマ、引用符なしのキー、コメント、Python リテラル等。JSON を貼って Repair & Format をクリックすれば一手でクリーンな妥当 JSON が返ります。すべてブラウザで実行され、サーバには何も送信されません。