Laravel『Invalid JSON Response』の原因と対処法
- 作成日 2026.01.05
- その他
「Invalid JSON Response」は、Laravel(またはフロント側)が“JSONを期待しているのに、JSONとして解釈できないレスポンスが返ってきた”ときに起きる。原因はLaravel側の例外でHTMLエラーページが返っている、BOMや余計な出力(echo/var_dump)が混ざっている、文字コード不正、JSONにできない値(INF/NaN、循環参照、巨大整数の扱い)、ヘッダが不正、gzip/プロキシで本文が壊れている、などが多い。まずは「実際に返ってきたレスポンス本文」と「HTTPステータス」「Content-Type」を確認し、JSON以外が混ざる経路を潰す。
- 1. 症状と発生条件(典型例)
- 2. まず確認:HTTPステータス / Content-Type / 本文の先頭
- 3. 原因1:例外が起きてHTMLエラーページが返っている(JSON期待なのにHTML)
- 4. 原因2:未認証/CSRFでログイン画面(HTML)にリダイレクトされている
- 5. 原因3:余計な出力が混ざってJSONが壊れている(echo/var_dump/BOM/空白)
- 6. 原因4:JSONにできない値(Malformed UTF-8 / INF・NaN / 循環参照)
- 7. 原因5:Content-Typeやレスポンス形式の不一致(JSON以外を返している)
- 8. サンプル:常にJSONを返すAPIエンドポイント(成功/失敗で形式固定)
- 9. チェックリスト(上から順に確認する)
症状と発生条件(典型例)
発生条件の典型:
・Axios/fetchで res.json() を呼んだらパースに失敗する
・APIクライアントで「Unexpected token < in JSON」などが出る(HTMLが返っている合図)
・Laravel側で JsonResponse の生成時に例外(InvalidArgumentException / JsonException)になる
よくある見え方(例)。
Unexpected token < in JSON at position 0
Invalid JSON response
The response is not a valid JSON response.
Malformed UTF-8 characters, possibly incorrectly encoded
「<」から始まる場合はHTML(エラーページ/ログイン画面)が返っている可能性が高い。
まず確認:HTTPステータス / Content-Type / 本文の先頭
最初に“本当にJSONが返っているか”を確定する。
curl -i https://example.com/api/endpoint見るポイント:
・Status: 200/401/403/419/500 など
・Content-Type: application/json になっているか
・本文の先頭が「{」または「[」か(「<」ならHTML)
・Locationヘッダがあるならリダイレクト(ログイン画面へ飛ばされている等)
原因1:例外が起きてHTMLエラーページが返っている(JSON期待なのにHTML)
APIルートで例外が起きると、環境やヘッダ次第でHTMLが返ってJSONパースで落ちる。
発生条件の典型:
・500エラー(SQL/Null/権限など)でHTMLのスタックトレース(またはエラーページ)が返る
・Acceptヘッダが application/json になっていない
対処:
・APIリクエストは Accept: application/json を付ける
curl -i -H "Accept: application/json" https://example.com/api/endpoint・Laravel側でAPIの例外応答をJSONに寄せる(expectsJsonを前提にする)
// app/Exceptions/Handler.php(例)
public function render($request, Throwable $e)
{
if ($request->expectsJson()) {
return response()->json([
'message' => $e->getMessage(),
], method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500);
}
return parent::render($request, $e);
}根本は「例外の原因を潰す」だが、まずJSONで返るようにすると原因特定が速い。
原因2:未認証/CSRFでログイン画面(HTML)にリダイレクトされている
APIを叩いたつもりが、実際は web ミドルウェアや auth によってログイン画面へ飛ばされ、HTMLが返ってJSONパースが壊れる。
典型ステータス:
・401(未認証)
・302(ログインへリダイレクト)
・419(CSRF/セッション期限)
対処:
・APIは routes/api.php を使い、webセッション前提の挙動を避ける
・認証が必要なら Bearer トークン等を付ける
・SPA + Cookie 認証なら credentials/CSRF を満たす
APIで未認証時にJSONで返したい場合。
// app/Exceptions/Handler.php
use Illuminate\Auth\AuthenticationException;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['message' => 'Unauthenticated'], 401);
}
return redirect()->guest(route('login'));
}原因3:余計な出力が混ざってJSONが壊れている(echo/var_dump/BOM/空白)
JSONの前後に文字が混ざると無効になる。
発生条件の典型:
・ControllerやMiddlewareで echo / var_dump / print_r を残している
・PHPファイルの先頭にBOM(UTF-8 BOM)が付いている
・レスポンスを返す前に何か出力している(obが絡む)
対処:
・デバッグ出力は logger() に置き換える
logger()->debug('debug', ['x' => $x]);・PHPファイルは「UTF-8(BOMなし)」で保存
・return response()->json(…) の前後で出力していないか確認
原因4:JSONにできない値(Malformed UTF-8 / INF・NaN / 循環参照)
Laravelのjsonエンコードで落ちる代表。
発生条件の典型:
・不正なバイト列が混ざっている(外部入力、DBの文字化け、バイナリ)
・浮動小数で INF / -INF / NaN が入っている
・オブジェクトが循環参照している(入れ子がループ)
対処:
・UTF-8に正規化する(保存前に検証、もしくは返却前に除外)
・INF/NaNが入り得る計算結果をnullにする等のルール化
・Eloquentモデルはそのまま返さず、Resourceで返却フィールドを制御する
use App\Http\Resources\UserResource;
return new UserResource($user);循環参照は「モデル同士を丸ごと配列化」すると起きやすいので、必要フィールドだけ返す。
原因5:Content-Typeやレスポンス形式の不一致(JSON以外を返している)
フロントがJSON前提なのに、Laravelが view() を返している、redirect() を返している、文字列を返している、など。
発生条件:
・成功時はJSONだが、失敗時はredirect/backでHTMLになる
対処:APIは成功/失敗ともにJSONで統一する。
// OK: バリデーション失敗もJSONで返す(FormRequest + API前提)
return response()->json([
'ok' => true,
'data' => $data,
]);バリデーションはFormRequestで expectsJson が効くようにするか、API専用のレスポンスに統一する。
サンプル:常にJSONを返すAPIエンドポイント(成功/失敗で形式固定)
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
Route::post('/api/profile', function (Request $request) {
try {
$data = $request->validate([
'name' => ['required', 'string'],
'age' => ['nullable', 'integer'],
]);
// 何らかの処理…
return response()->json([
'ok' => true,
'data' => $data,
], 200);
} catch (ValidationException $e) {
return response()->json([
'ok' => false,
'message' => 'Validation failed',
'errors' => $e->errors(),
], 422);
} catch (\Throwable $e) {
return response()->json([
'ok' => false,
'message' => 'Server error',
], 500);
}
});この形にすると、フロント側は常にJSONとして扱え、パース失敗の調査がほぼ不要になる。
チェックリスト(上から順に確認する)
1) curl -i で Status / Content-Type / 本文先頭({ か < か)を確認したか
2) Accept: application/json を付けたときに挙動が変わるか(HTML→JSONになるか)
3) 401/302/419 なら未認証・CSRF・セッションの問題でHTMLに飛ばされていないか
4) echo/var_dump/BOM/余計な空白がレスポンスに混ざっていないか
5) Malformed UTF-8 / INF/NaN / 循環参照など “JSON化できない値” が含まれていないか
6) 失敗時だけ redirect()/view() を返していないか(成功/失敗ともJSONで統一できているか)
-
前の記事
Laravel『Too Many Redirects(リダイレクトループ)』の原因と対処法 2026.01.01
-
次の記事
Laravel『Undefined Offset』の原因と対処法 2026.01.07
コメントを書く