0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【FastAPI】Invalid args for response field! の原因と解決 — 戻り値Unionに JSONResponse を混ぜたとき

0
Last updated at Posted at 2026-06-27

FastAPI のエンドポイントに「辞書を返す正常系」と「JSONResponse を返すエラー系」の両方を持たせたくて、戻り値の型ヒントを dict | JSONResponse にした——その瞬間、アプリが起動すらしなくなりました。原因は型ヒントに Response 系のクラスを混ぜたことです。

結論(先に解決策)

そのエンドポイントのデコレータに response_model=None を付けるだけで直ります。

# NG: 戻り値 Union に JSONResponse を含めると起動時に落ちる
@router.get("/basic")
async def get_basic(...) -> dict[str, Any] | JSONResponse:
    ...

# OK: 応答モデルの自動生成を止める
@router.get("/basic", response_model=None)
async def get_basic(...) -> dict[str, Any] | JSONResponse:
    ...

FastAPI は戻り値の型ヒントから「レスポンスモデル」を自動で組み立てようとしますが、JSONResponse のような Response 派生クラスは Pydantic のフィールドにできません。response_model=None でその自動生成を明示的に無効化します。

症状

サーバー起動時(importの解決時)に、エンドポイントを呼ぶ前から例外で落ちます。

fastapi.exceptions.FastAPIError: Invalid args for response field!
Hint: check that dict[str, Any] | starlette.responses.JSONResponse
is a valid Pydantic field type

再現条件はシンプルで、戻り値の型ヒント(Union)に Response / JSONResponse などの応答クラスを含めているときです。

from fastapi import APIRouter
from fastapi.responses import JSONResponse

router = APIRouter()

@router.get("/basic")
async def get_basic() -> dict[str, Any] | JSONResponse:  # ← これが原因
    if error:
        return JSONResponse(status_code=400, content={"status": "error"})
    return {"status": "ok", "data": [...]}

原因

FastAPI は、戻り値の型ヒントを使って レスポンスのスキーマ(response_model)を自動生成します。生成の実体は Pydantic のフィールド構築です。

  • dict[str, Any] は Pydantic フィールドにできる
  • しかし JSONResponse(= Starlette の Response 派生)は Pydantic のフィールド型として無効

Union の中に1つでも「Pydantic 化できない型」が混じると、フィールド全体の構築に失敗し、Invalid args for response field! を投げます。エラー文に Union の中身がそのまま出るので、... | starlette.responses.JSONResponse の部分が犯人だと分かります。

解決

応答モデルの自動生成を止めれば、型ヒントは「人間向けの注釈」として残しつつ FastAPI の検証対象から外れます。

@router.get("/basic", response_model=None)
async def get_basic() -> dict[str, Any] | JSONResponse:
    if error:
        return JSONResponse(status_code=400, content={"status": "error", "message": "..."})
    return {"status": "ok", "data": [...]}

これで起動も通り、正常系は辞書、エラー系は JSONResponse という分岐もそのまま使えます。

代替案:そもそも JSONResponse を戻り値に混ぜない

「正常系・エラー系で共通の辞書形を返す」設計なら、JSONResponse を返さずに HTTPException を投げる方がきれいです。

from fastapi import HTTPException

@router.get("/basic")
async def get_basic() -> dict[str, Any]:
    if error:
        raise HTTPException(status_code=400, detail="Invalid area_code")
    return {"status": "ok", "data": [...]}

この形なら戻り値は dict 単独になり、response_model=None も不要です。ステータスコードや本文を細かく自前制御したい場合のみ JSONResponseresponse_model=None を選ぶ、という棲み分けが分かりやすいです。

まとめ

  • Invalid args for response field!戻り値の型ヒントを Pydantic 化できないときに出る
  • Union に Response / JSONResponse を混ぜたのが典型例
  • 即効薬は @router.get(..., response_model=None)
  • 設計を見直せるなら HTTPException でエラーを投げて戻り値を dict 単独にする

なお、この症状は e-Stat(政府統計)の人口データを FastAPI と PHP の二重実装で公開する API を作る途中で踏みました。正常時もエラー時も同じ {"status": ..., "data": ..., "meta": ...} 形に揃えるレスポンス設計の話は、別途まとめる予定です。

Claude Codeで開発したサイト

人口カルテ47

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?