JSON エラーメッセージ早見表:エラー文言から修正まで直行
要点:JSON.parse の正確なメッセージから原因を引きます。Chrome・Firefox・Safari にまたがる 12 の頻出エラー、HTML エラーページ、途中切れ、二重パースを含みます。

要点:JSON.parse のエラーが出たら、まず position と token を拾いましょう。position が 0 なら HTML レスポンス・BOM・型違いを疑い、「Expected」のメッセージなら前の文字を見ます。報告された位置から行ごとに読み直すのはやめましょう。
エラー文言は雑音ではありません。答えそのものです。パーサーはぶつかった文字、期待していたもの、実際に受け取ったものを教えてくれます。エラーメッセージで検索する方が、文書全体を手で書き直すより速いのです。
JSON.parse() のエラーには必ず三つの要素があります。位置(文字オフセット、または行・列)、期待(Expected...)、現実(Unexpected token X、または xxx is not valid JSON)です。エンジンによって言い回しは異なります。Chrome と Node(V8)は Expected ',' or '}' after property value in JSON at position 25、Firefox は JSON.parse: expected ',' or '}' after property value in object at line 3 column 1、Safari は JSON Parse error: Expected ',' or '}' と言いますが、どれも同じ構文の破綻を指しています。エラーを調べるときは文言を無視し、三つの事実、つまりどの文字か、何を期待していたか、何が届いたかを拾ってください。
1. 12 エラー早見表
左列はコンソールから最も識別しやすい断片です(V8 の文言)。Firefox / Safari の変種は括弧内に示します。
| エラー文言(V8) | 実際の原因 | 修正 |
|---|---|---|
Unexpected token < in JSON at position 0 |
API が JSON ではなく HTML を返した(404 ページ、500 テンプレート、ログインリダイレクト、プロキシのフォールバック) | 先に response.status と Content-Type を確認する。JSON を直さない |
Unexpected end of JSON input |
入力が空または途中で切れている。空のボディ、204、中断されたダウンロード、最終行を欠いたコピー | 空の場合を処理し、Content-Length と比較して切れ目を探す |
Unexpected token o / "[object Object]" is not valid JSON |
すでにパース済みのオブジェクトを再度 JSON.parse() に渡し、文字列に変換された |
typeof を確認する。オブジェクトならそのまま使う |
"undefined" is not valid JSON |
変数が一度も代入されていない(JSON.parse(undefined)) |
フォールバック値を初期化し、パース前にガードする |
Expected double-quoted property name in JSON |
末尾カンマ({"a": 1,})。パーサーは次のキーを期待し、} に出会う |
最後の項目の後のカンマを削除する |
Expected property name or '}' in JSON |
引用符なしまたは単一引用符のキー({name: "Ada"}、{'name': 'Ada'}) |
すべてのキーを二重引用符で囲む |
Expected ',' or '}' after property value in JSON |
カンマの欠落({"a": 1 "b": 2})、または値の後のコメント |
カンマを追加する。コメントを除去する |
Unexpected token '/' |
JSON 内の // または /* */ コメント |
削除するか、設定を JSONC としてパースする |
Bad control character in string literal |
文字列内の生の改行、タブ、その他の U+0000–U+001F 制御文字 | \n と書く。JSON.stringify で生成する |
Unterminated string in JSON |
" で開いた文字列が閉じられていない、または内側の引用符がエスケープされていない |
内側の引用符を \" とエスケープする |
Unexpected non-whitespace character after JSON |
一つのテキストに二つの文書:{}{}、NDJSON、JSONP ラッパー |
行で分割して個別にパースするか、NDJSON パーサーを使う |
JSON.parse is not a function |
独自の変数がグローバルの JSON を隠している |
変数名を変更する。JSON という名前は使わない |
最初の二つは詳しく見る価値があります。どちらも「あなたの JSON が間違っている」ではなく、「受け取ったものは思っているものと違う」という意味だからです。
2. position 0 エラーの三つのよくある原因
position が 0 なら、最初の文字が不正です。何も怪しく見えなければ、その文字は不可視である可能性が高いです。容疑者は三つ。
第一:<、つまり HTML。 本番で最もよくあるパースエラーです。URL の誤り、期限切れセッションのゲートウェイによるリダイレクト、サーバーエラーテンプレート。ボディが <!DOCTYPE html> で始まり、パーサーは一文字目で止まります。正しい対応は Network パネルを開いて生のレスポンスを読み、JSON ではなく API 呼び出しを直すことです。
第二:BOM またはゼロ幅文字。 Windows のメモ帳で保存したファイルはしばしば UTF-8 BOM(\uFEFF)で始まり、Web ページやチャットアプリからコピーしたテキストはゼロ幅スペース(\u200B–\u200D)を含むことがあります。あなたには見えませんが、パーサーには明らかです。
// パース前に BOM とゼロ幅文字を除去する
const cleaned = raw.replace(/^\uFEFF/, '').replace(/[\u200B-\u200D]/g, '');
const data = JSON.parse(cleaned);
第三:型違い。 JSON.parse(undefined) は "undefined" is not valid JSON を報告し、JSON.parse(someObject) は Unexpected token o を報告します(オブジェクトが "[object Object]" に変換され、o が位置 1 で不正になります)。どちらも「データはすでにパース済み」または「そもそもデータがなかった」という意味です。構文ではなく型を確認してください。
let data = text;
if (typeof data === 'string') {
data = JSON.parse(data); // 本当に文字列のときだけパースする
}
3. 位置は下流を指し、原因は上流にある
位置について誤解を招くエラーが二つあり、どちらも早見表で最も読み違えられる項目です。
- カンマの欠落:
{"a": 1 "b": 2}は"b"の行でExpected ',' or '}' after property valueを報告します。報告は正しいのですが、修正すべき場所は前の行の末尾です。 - 閉じ括弧の欠落:深いネストで
}や]が一つ足りないと、パーサーは入力の最後まで飲み込み、Unexpected end of JSON inputを報告します。位置はファイル末尾ですが、本当の欠落はずっと前にあることがあります。直近の閉じていない{または[を後方に探しましょう。対応を数える方が目視より確実です。
一般的な手法は二分探索です。報告された位置より前のテキストを切り出して単独でパースします。パースできれば問題は報告位置の近くにあり、できなければさらに前で二分します。最初に失敗する断片の境界が本当のエラー位置です。大きなファイルではまず整形してください。インデントが揃っていれば、足りない階層が一目で分かります(オンラインの JSON フォーマッタで十分です)。
4. パースは通るが挙動がおかしい四つの「見かけの正常」
エラーより厄介なのは、エラーが出ないのにデータが間違っている状態です。次の四つはいずれも JSON として正しく、パーサーは満足しています。問題はデータの形にあります。
- 二重エンコード:文書全体が二度
JSON.stringifyされ、パース結果がオブジェクトではなく文字列になる。typeof result === 'string'ならもう一度パースしますが、根本では二重シリアライズを見つけて直してください。 - 重複キーが無言で上書き:
{"name": "Tom", "name": "Jerry"}は正しい JSON で、ほとんどのパーサーは文句を言わず、後ろの値が勝ちます。複数ソースのデータを統合するときに刺されます。刺される前に厳格な JSON バリデータで表面化させましょう。 - NDJSON を一文書として扱う:ストリーミング API は 1 行に 1 オブジェクトを出力します。テキスト全体をパースすると
Unexpected non-whitespace character after JSONで失敗します。\nで分割し、行ごとにパースします。 - 数値精度の静かな損失:
2^53 - 1を超える整数(スノーフレーク ID)は、末尾の桁がゼロになり、エラーは一切出ません。ID は文字列で送ります。
共通するのは、パーサーはすべて正常だと信じていることです。ですから API のデータがおかしく見えたら、まず生テキストの先頭 80 文字と typeof を出力し、内容の議論の前に形を確認してください。
5. 書き込み時にエラーを起こりにくくする
早見表は事後の道具で、予防は事前の道具です。四つの習慣がほとんどのエラーを発生前に食い止めます。
// 1. レスポンスをパースする前の二つの確認
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: API がデータを返しませんでした`);
}
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.includes('application/json')) {
throw new Error(`JSON を期待しましたが、${contentType} を受け取りました`);
}
const data = await response.json(); // 2. json() がすでにパースする。再度パースしない
- 連結ではなくシリアライズで生成する:
JSON.stringify()は引用符、改行、制御文字を正しく処理します。手組みの文字列はBad control characterとUnterminated stringの最大の発生源です。 - 必ず catch する:投げられた
JSON.parseエラーは、誰かが捕まえない限り同期的なクラッシュです。localStorage や外部ファイルのような信頼できない入力では、catch ブロックに既定値か明示的なエラー報告が必要です。 - 構造は Schema に任せる:構文が正しいことは構造が正しいことを意味しません。フィールド欠落や型違いは境界での JSON Schema 検証の仕事です。JSON Schema が解決するものを参照してください。
6. 素早い判断経路
記事全体を一つの判断順に圧縮します。エラーを見たら、まず position と token を拾います。
- position が 0 →
<(HTML)、BOM、型違いの三つを確認。 end of input→ 空または切れたボディを確認し、閉じていない括弧を後方に探す。Expected ...→ 前の文字を見る。たいていカンマか引用符の欠落。after JSON data→ 複数の文書が連結されている。行で分割。- パースは通るがデータがおかしい → 四つの「見かけの正常」チェックを実行。
JSON パースエラーの直し方の段階的な切り分けと組み合わせれば、パース問題の 90% は五分以内に解決します。エラー文言そのものをブックマークしておきましょう。それはパーサーがあなたのために書いたデバッグログで、言い回しは三年に一度変わる程度。一度引けば一年分の価値があります。
「Unexpected token in JSON」とはどういう意味ですか?
パーサーが、JSON 文法上その位置では不正な文字にぶつかったという意味です。token は拒否された文字です。< は通常 API が HTML を返したこと、' は単一引用符、/ はコメント、普通の英字は引用符なしキーや undefined、NaN のような JavaScript 専用リテラルを意味します。まず token、次に position を読み、原因を一つに絞り込みます。
「Unexpected end of JSON input」はどう直しますか?
原因は二つ。空の入力(空のレスポンスボディ、localStorage の空文字列)か、切れた入力(中断されたダウンロード、最終行を欠いたコピー、サイズ制限)です。空の場合は if (!text) return で処理し、その後で完全性を確認し、直近の閉じていない {、[、" を閉じます。深い階層の括弧欠落でもこのエラーが出ることがあり、位置がたまたまファイル末尾なだけです。
Chrome、Firefox、Safari でエラーが違うのはなぜですか?
三つのブラウザは異なる JS エンジン(V8、SpiderMonkey、JavaScriptCore)を積んでおり、それぞれ独自の言い回しでエラーを出します。MDN が公式の変種をすべて記録しています。しかし三つのエラーは位置・期待・現実という同じ三要素を、一対一の意味で運んでいます。V8 の文言に翻訳して、この早見表をそのまま使ってください。
エラー位置が正常に見える場所を指しているときは?
本当のエラーはもっと前にあります。典型例はカンマの欠落(次の行で報告)と閉じ括弧の欠落(ファイル末尾で報告)です。報告位置から token 単位で後方に辿るか、二分探索します。前半だけをパースし、最初に失敗する断片の境界が真のエラー位置です。