PC・モバイルのトラブル
JSON.parseが「Unexpected token」と言うのはなぜ?
テキストが有効なJSONでないとき、JSON.parseはUnexpected tokenを投げる。単引用符、末尾カンマ、HTML応答、エラー位置を切り分ける。
入力が有効なJSONでないとき、JSON.parse()はSyntaxErrorを投げる。「Unexpected token」は、その位置の構造では許されない文字にパーサーが達した、という意味が多い。
よくある原因は、単引用符、末尾カンマ、引用符なしのプロパティ名、誤った文字列エスケープ、JSONの前後の余分なテキスト、HTMLなど非JSONの本体をJSONとして読むことだ。
正確なエラー文はJavaScriptエンジンとブラウザで違う。「Unexpected token X in JSON at position N」がどのランタイムでも出ると思い込まないこと。
これはJSON.parseのUnexpected tokenの切り分けだ。エラーの意味、positionの読み方、本体がHTMLのときの対処。末尾カンマ・引用符・Unexpected endの幅広い解説はよくあるJSONエラー:末尾カンマ、引用符、Unexpected tokenにある。
JSON.parse()の「Unexpected token」は何を意味するか?
JSON.parse()はJSONの文法を要求する。現在位置の不正な文字はSyntaxErrorになる。{“name”:“Alex”,} は無効だ。そのカンマのあと、オブジェクトは終われない。メッセージとpositionはランタイムでまだ違う。
JSON.parse()はどのエラーを投げるか?
無効なJSONはおおむねSyntaxErrorを投げる。型を確かめるにはerror.nameをログする。error.messageの全文をハードコードしないこと。エンジンと版で文言が違う。
try {
JSON.parse('{"name":"Alex",}');
} catch (error) {
console.log(error.name);
// SyntaxError
}
1. 二重引用符の代わりに単引用符を使う
{‘name’:‘Alex’} は無効なJSONだ。{“name”:“Alex”} は有効だ。JSONの文字列とオブジェクトのメンバー名は二重引用符を使う。const user = { name: ‘Alex’ } のようなJavaScriptオブジェクトリテラルは、合法なJavaScriptでもJSONテキストではない。
無効
{'name':'Alex'}
有効
{"name":"Alex"}
2. 末尾カンマ
最後のプロパティや配列要素のあとのカンマは、標準JSONでは無効だ。{“name”:“Alex”,“age”:30,} も [1, 2, 3,] も失敗する。Unexpected tokenの原因の一つとして扱う。末尾カンマの詳しい話はよくあるJSONエラー:末尾カンマ、引用符、Unexpected tokenにある。
無効
{
"name": "Alex",
"age": 30,
}
有効
{
"name": "Alex",
"age": 30
}
[1, 2, 3,]
3. 引用符なしのプロパティ名
{ name: “Alex” } はJavaScriptのオブジェクトリテラルになり得る。JSONではオブジェクトのメンバー名は文字列であり、二重引用符が要る:{“name”:“Alex”}。
無効
{
name: "Alex"
}
有効
{
"name": "Alex"
}
4. 不正な文字列エスケープ
JSONテキストではバックスラッシュがエスケープの開始だ。C:\Users\Alex のような生のパスは無効で、\U は合法なエスケープではない。バックスラッシュを二重にする。ホスト言語の文字列エスケープとJSONのエスケープを混ぜない。詳しくはよくあるJSONエラー:末尾カンマ、引用符、Unexpected token。
無効
{"file":"C:\Users\Alex"}
有効
{"file":"C:\\Users\\Alex"}
5. JSONの前や後の余分なテキスト
JSON.parseはJSON値を一つ期待する。{“ok”:true} の前の Success: や、後ろの done があると、文字列全体が無効になる。一つのJSON値のまわりの意味のない空白は許される。値の前後にスペースがあること自体は、JSONで禁じられていない。
Success:
{"ok":true}
{"ok":true}
done
6. JSONの代わりにHTMLを解析する
HTMLページに対してresponse.json()を呼ぶのは、Unexpected tokenのよくある例だ。誤ったエンドポイント、404や500のHTML、ログインリダイレクト、プロキシの本体、フォールバックページは、マークアップで始まることが多い。「Unexpected token < は常にHTML」は言い過ぎだ。< は手がかりであり、証明ではない。
fetch('/api/data')
.then(response => response.json())
「Unexpected token <」はたいてい何を意味するか?
JSON値はふつう < で始まらない。本体が や なら、< が最初の不正文字になり得る。ローカルの切り分けでは、HTTPステータス、Content-Type、生の本体を見る。エンジンの文言はなお違う。
解析する前に応答を確認する
fetchの本体は一度しか消費できない。ローカルの切り分けでは、先にtext()を読み、短い先頭を見て、同じ文字列をJSON.parseする。同じ応答でresponse.json()のあとresponse.text()を呼んではいけない。本番の機微なペイロードを既定でログしない。
const response = await fetch(url);
const text = await response.text();
console.log(response.status);
console.log(response.headers.get("content-type"));
console.log(text.slice(0, 200));
try {
const data = JSON.parse(text);
console.log(data);
} catch (error) {
console.error("Invalid JSON:", error);
}
「at Position 123」は何を意味するか?
エンジンによってはposition、行、列を報告する。近くの誤った引用符、カンマ、波括弧、エスケープを見る。その番号は、解析が問題に気づいた位置だ。本当の原因はより手前にあり得る。故障した文字そのものとは限らない。
Unexpected token と Unexpected end
Unexpected tokenは、パーサーが予期しない文字に出会ったという意味だ。{“name”:“Alex” のように閉じ括弧がないなど、値が完了する前に入力が止まると Unexpected end が出ることがある。正確な文言はエンジン依存だ。不完全なJSONの広い話はよくあるJSONエラー:末尾カンマ、引用符、Unexpected tokenにある。
{"name":"Alex"
JSONはJavaScriptオブジェクトリテラルと同じではない
JSONはデータ形式だ。JavaScriptオブジェクトリテラルはJavaScriptの構文だ。引用符なしキー、単引用符、末尾カンマのあるJSオブジェクトは、有効なJSONテキストではない。JSON.parseはJavaScriptオブジェクトリテラルを安全に解析できない。
const data = {
name: 'Alex',
active: true,
};
{
"name": "Alex",
"active": true
}
JSONにコメントを含めてよいか?
標準JSONはコメントをサポートしない。オブジェクト内の // 行は無効だ。JSON5や類似の拡張は別形式だ。JSON整形・検証・比較はJSON.parseで標準JSONを検証し、JSON5をJSONとしては扱わない。
{
// user name
"name": "Alex"
}
undefined、NaN、Infinityは有効なJSONか?
いいえ。JSONの値はobject、array、string、number、true、false、nullだ。undefined、NaN、Infinityはそうではない。トップレベルの値はオブジェクトや配列でなくてよい。true、123、“hello”、nullは有効なJSONだ。
{"a":undefined}
{"a":NaN}
{"a":Infinity}
true
123
"hello"
null
Unexpected tokenエラーを順に直す方法
エラー文字列ではなく、ソースを扱う:
- 入力が本当にJSONか確認する。HTML、ログ、JavaScriptではないか。
- 生入力の先頭文字を見る。
- API由来なら、HTTPステータスとContent-Typeを見る。200でもapplication/jsonでも、有効なJSONは保証されない。
- JSONパーサーで構文を検証する。
- 報告されたposition、行、列は手がかりであり、保証された根本原因ではない。
- その付近の引用符、カンマ、波括弧、角括弧、エスケープを確認する。
- 整形され検証済みのコピーと比較する。
- シリアライザかエンドポイントを直す。報告されたトークンを無作為に消さない。
' を " へ一括置換すると、文字列内のアポストロフィ、既にエスケープされたテキスト、非JSONのソースを壊し得る。本物のシリアライザから有効なJSONを出す。evalをJSONパーサーとして扱わない。
JavaScriptでJSONを出すときはJSON.stringify()を使う
手で文字列を連結するよりJSON.stringifyを優先する。シリアライズする値については有効なJSONテキストを出す。すべてのJavaScript値を保つわけではない。undefined、関数、シンボルには特別な挙動がある。その詳細はこの記事の範囲外だ。
const data = {
name: "Alex",
active: true
};
const json = JSON.stringify(data);
// {"name":"Alex","active":true}
無効なJSONと本当の修正
これらの対はJSON.parseで確認した。HTMLでは、修正は応答であり、構文の編集ではない。
| 問題 | 無効例 | 直し方 |
|---|---|---|
| 単引用符 | {'a':1} |
{"a":1} |
| 末尾カンマ | {"a":1,} |
{"a":1} |
| 引用符なしキー | {a:1} |
{"a":1} |
| undefined | {"a":undefined} |
{"a":null} |
| HTML本体 | <html>... |
エンドポイントと生の応答を確認する。HTMLをJSONへ編集しない。 |
Unexpected tokenの簡単な判断ガイド
JSON.parseが失敗したら、引用符を書き直す前に、そもそもJSONがあるかを決める。
- JSON.parseが失敗した
- API由来か。ステータス、Content-Type、生の本体を確認する
- JSONで始まっているか。そうでなければソースかエンドポイントを直す
- そうなら、引用符、カンマ、波括弧、角括弧、エスケープ、未対応の値を検証する
- 報告されたpositionを手がかりにする
- ソースを直して、もう一度解析する
解析や利用の前にJSONを検証する
NEXNARA JSON整形・検証・比較はブラウザでJSON.parseによりJSONを検証する。evalではない。
検証、2スペース・4スペース・タブでの整形、minify、コピー、ダウンロード、.jsonのアップロード。JSON Diffは入れ子のオブジェクトと配列、パス、要約、フィルタ、入れ替えに対応する。解析後、同じデータの整形版とminify版は等しいと比較される。オブジェクトのキーは名前で比較する。
5 MBより大きいテキストは遅く感じることがある。15 MBより大きいテキストはブラウザで処理しない。
JSONの検証と整形はブラウザ内で行う。このツールはJSONをNEXNARAサーバーへ送らない。広告や他のサイト機能はネットワークを使う。
失敗した文字列をJSON整形・検証・比較に貼り、引用符を書き直す前に先頭文字を読んでほしい。
FAQ
JSON.parseがUnexpected tokenと言うのはなぜ?
パーサーが止まった位置で、テキストが有効なJSONではない。典型は引用符、カンマ、余分なテキスト、HTMLなどの非JSON本体だ。
JSONのUnexpected token < は何を意味するか?
最初の不正文字が < のことがある。HTMLページを指すことが多いが、どの場合も404やHTMLの証明ではない。ステータス、Content-Type、生の本体を確認する。
JSONのエラー位置はどう見つける?
エンジンがposition、行、列を報告したら、その近くを見る。本当の誤りはより手前にあり得る。手がかりとして使い、それから検証する。
JSONは単引用符を使えるか?
いいえ。標準JSONの文字列とメンバー名は二重引用符を使う。
JSONに末尾カンマは置けるか?
いいえ。標準JSONは最後のプロパティや配列要素のあとのカンマを許さない。
文字列が有効なJSONかどうかはどう確認する?
try/catchでJSON.parseを呼ぶか、テキストをJSON整形・検証・比較に貼る。解析がSyntaxErrorを投げれば、その文字列は有効なJSONではない。