FastAPIで任意の有効なJSONをボディとして読み取る方法
FastAPIでリクエストボディを任意の有効なJSON(オブジェクトや配列だけでなく、数値、文字列、ブール値、nullなどの任意のJSON値)として受け取りたい場合があります。この記事では、様々な方法とその使い分けについて解説します。
問題点
Pydanticモデルを使用すると特定の構造が強制されますが、任意のJSON構造を受け入れたい場合があります。例えば:
- 単純な値:
42
,"hello"
,true
,null
- 複雑な構造:
{"key": [1, "text", null]}
- 任意のネスト構造
解決策
1. Bodyパラメータを使用する方法(推奨)
FastAPIの組み込み機能である Body
パラメータを使用するのが最も簡単で効率的な方法です。
python
from typing import Any
from fastapi import Body, FastAPI
app = FastAPI()
@app.post('/test')
async def update_item(payload: Any = Body(...)):
return payload
ポイント
Body(...)
はリクエストボディが必須であることを示しますBody(None)
とするとオプショナルなボディになります- 無効なJSONの場合は自動的に検証エラーが返されます
2. Annotatedを使用する方法(Python 3.9+)
型ヒントを明示的にする場合は Annotated
を使用します。
python
from typing import Any, Annotated
from fastapi import Body, FastAPI
from pydantic import Json
app = FastAPI()
@app.post("/test")
async def get_body(payload: Annotated[Any, Body()]):
return payload
3. Requestオブジェクトを使用する方法
低レベルな制御が必要な場合は、Requestオブジェクトから直接ボディを取得します。
python
from fastapi import Request, FastAPI
app = FastAPI()
@app.post("/dummypath")
async def get_body(request: Request):
# JSONとしてパース
json_data = await request.json()
return json_data
注意点
この方法では自動的なバリデーションが行われないため、不正なJSONでもエラーになりません。必要に応じて自前でエラーハンドリングを実装する必要があります。
テスト例
以下のテストコードで各種JSON形式の動作を確認できます。
python
import json
import pytest
from fastapi.testclient import TestClient
client = TestClient(app)
@pytest.mark.parametrize(
("payload", "expected"),
[
pytest.param('{"key": 1}', {"key": 1}, id="simple json"),
pytest.param(json.dumps({"key": None}), {"key": None}, id="json with None"),
pytest.param(json.dumps([1, 2, 3]), [1, 2, 3], id="json list"),
pytest.param(json.dumps([{"a":1}, {"b": 2}, 3]), [{"a":1}, {"b": 2}, 3], id="json list of mixed types"),
pytest.param("{}", {}, id="empty json"),
pytest.param("[]", [], id="empty json list"),
pytest.param(json.dumps(6), 6, id="just a number"),
pytest.param('"hello"', "hello", id="just a string"),
pytest.param('true', True, id="just a boolean"),
pytest.param('null', None, id="just a null"),
]
)
def test_get_body(payload, expected):
response = client.post("/test", data=payload)
assert response.status_code == 200
assert response.json() == expected
その他の方法(非推奨)
Dictを使用する方法
python
from typing import Dict, Any
from fastapi import FastAPI
app = FastAPI()
@app.post("/dict/")
async def post_dict(data: Dict[str, Any]):
return data
制限事項
この方法ではJSONオブジェクトのみ受け付けられ、単純な値(数値、文字列など)は処理できません。
まとめ
FastAPIで任意のJSONを受け取るには、以下の方法を使い分けるのが良いでしょう:
- 標準的なケース:
Any = Body(...)
を使用 - 型ヒントを明確にしたい場合:
Annotated[Any, Body()]
を使用 - 低レベルな制御が必要な場合: Requestオブジェクトから直接取得
最初の2つの方法が自動的なバリデーションを提供し、最も安全で便利です。用途に応じて適切な方法を選択してください。