本文へスキップ
jsonbeautifiers
日本語
V8 Node v24.15.0, V8 13.6.233.17 上でパーサーを実行して確認しました。

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 の許可リストにより、これは約束ではなくブラウザによる保証になっています。 自分で確かめる

実際の原因

どれが答えになることが多いか、その順に並べています。

  1. 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();
  2. 02 シングルページアプリが未知のパスをindex.htmlに書き換えた

    開発中でもっともよくある原因です。Vite、Create React App、そして多くの開発サーバーは、認識できないパスに対してindex.htmlを返します。したがってAPIルートのタイプミスやプロキシ設定の不足があると、自分のアプリがHTMLとしてステータス200で返ってきます。res.okの確認だけでは足りない理由がこれです。

  3. 03 URLが想定外の場所に解決された

    fetch("api/user")のような相対パスは現在のルートを基準に解決されるため、/settings/profileからは/settings/api/userを要求します。先頭にスラッシュを付けるか、絶対URLを使ってください。

    壊れる例

    fetch('api/user')

    動く例

    fetch('/api/user')
  4. 04 キャプティブポータル、VPN、プロキシがリクエストを横取りした

    ホテルのWi-Fi、社内プロキシ、Cloudflareのボットチャレンジは、自分のHTMLページを返します。一部のユーザーだけ、しかも一部のネットワークでだけ起きるエラーとして現れます。

  5. 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ヘッダーも確認してください。

今すぐ直す

上のツールにペイロードを貼り付けるか、この用途のために作られたツールに直接進んでください。

ペイロードを確認する