Unexpected token '<', "<!DOCTYPE "... is not valid JSON
あなたのコードは、HTMLドキュメントに対してJSON.parse(あるいはres.json())を呼びました。位置0にある山括弧は、<!DOCTYPE html>の開始です。JSONが壊れているのではなく、そもそもJSONを受け取っていません。直すべきものはパーサーではなく、リクエストのほうです。
次のような書き方で見かけることもあります
- Unexpected token < in JSON at position 0
- Unexpected token '<', "<html><hea"... is not valid JSON
- SyntaxError: Unexpected token < in JSON at position 0
V8は2022年に、JSONのエラーメッセージのほとんどを書き換えました。検索結果に出てくるのはいまだに古い文言がほとんどですが、現行のランタイムはどれもそれを出力しません。
JSONを貼り付けて、壊れている箇所を正確に確認する
貼り付けたものがブラウザの外に出ることはありません。 connect-src の許可リストにより、これは約束ではなくブラウザによる保証になっています。 自分で確かめる
実際の原因
どれが答えになることが多いか、その順に並べています。
-
01 サーバーがエラーページを返した
404、500、あるいはゲートウェイのエラーがHTMLとして描画されたものです。多くのフレームワークは、明示的にJSONを要求しないかぎり、APIのルートに対してもHTMLのエラーページを返します。ボディより先にステータスコードを確認してください。
壊れる例
const data = await fetch('/api/user').then((r) => r.json());動く例
const res = await fetch('/api/user', { headers: { Accept: 'application/json' } }); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); const type = res.headers.get('content-type') ?? ''; if (!type.includes('application/json')) { throw new Error(`Expected JSON, got ${type}: ${(await res.text()).slice(0, 200)}`); } const data = await res.json(); -
02 シングルページアプリが未知のパスをindex.htmlに書き換えた
開発中でもっともよくある原因です。Vite、Create React App、そして多くの開発サーバーは、認識できないパスに対してindex.htmlを返します。したがってAPIルートのタイプミスやプロキシ設定の不足があると、自分のアプリがHTMLとしてステータス200で返ってきます。res.okの確認だけでは足りない理由がこれです。
-
03 URLが想定外の場所に解決された
fetch("api/user")のような相対パスは現在のルートを基準に解決されるため、/settings/profileからは/settings/api/userを要求します。先頭にスラッシュを付けるか、絶対URLを使ってください。
壊れる例
fetch('api/user')動く例
fetch('/api/user') -
04 キャプティブポータル、VPN、プロキシがリクエストを横取りした
ホテルのWi-Fi、社内プロキシ、Cloudflareのボットチャレンジは、自分のHTMLページを返します。一部のユーザーだけ、しかも一部のネットワークでだけ起きるエラーとして現れます。
-
05 PHPがJSONの前に警告を出力した
レスポンスボディの前にnoticeやwarningが出力されると、ドキュメントは{ではなく<br />で始まります。JSONはHTMLの後ろにまだ残っています。本番ではdisplay_errorsをオフにしてください。
ほかのランタイムでの同じ間違い
根本にある問題は同一で、違うのは文言だけです。同僚がこれらのどれかを報告してきたなら、見ているものはあなたと同じです。
| Python | Expecting value: line 1 column 1 (char 0) |
|---|---|
| C# (Newtonsoft) | Unexpected character encountered while parsing value: <. Path '', line 0, position 0. |
| C# (System.Text.Json) | '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0. |
| Java (Jackson) | Unexpected character ('<' (code 60)): expected a valid value |
よくある質問
- なぜ「Unexpected token < in JSON at position 0」から文言が変わったのですか?
- V8は2022年に、問題となった入力の断片を含めるようJSONのエラーメッセージを書き換えました。Chrome 104以降とNode 18以降が新しい文言を出力します。古い文字列は、いまだに検索結果やStack Overflowの回答の多くが示すものですが、現行のランタイムはどれもそれを出力しません。
- 私のJSONが壊れているのですか?
- 違います。そもそもJSONは関わっていません。パーサーが読んでいるのはHTMLドキュメントです。リクエストが返すものを直せば、JSONに触れずともエラーは消えます。
- ステータスコードは200なのに、なぜHTMLなのですか?
- 未知のルートに対してindex.htmlへフォールバックする開発サーバーは、HTMLのボディを200で返します。ステータスだけでなくContent-Typeヘッダーも確認してください。