Skip to content

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を受け取るには、以下の方法を使い分けるのが良いでしょう:

  1. 標準的なケース: Any = Body(...) を使用
  2. 型ヒントを明確にしたい場合: Annotated[Any, Body()] を使用
  3. 低レベルな制御が必要な場合: Requestオブジェクトから直接取得

最初の2つの方法が自動的なバリデーションを提供し、最も安全で便利です。用途に応じて適切な方法を選択してください。