当サイトは、アフィリエイト広告を利用しています

【FastAPI】PydanticのTypeAdapterを理解する

作成日:2026月07月13日
更新日:2026年07月13日

FastAPIでは、入力値の検証にPydanticを使う。
リクエストボディを受け取る場合は、BaseModel を定義することが多い。

BaseModel
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
age: int
@app.post("/users")
def create_user(user: UserCreate = Body(...)):
return user

このような通常のAPI開発では、FastAPIがPydanticを使って自動で検証してくれる。
そのため、最初は BaseModel を理解していれば十分である。

しかし、実務では次のような場面がある。

  • list[int]
  • dict[str, str]
  • list[User]
  • Literal["asc", "desc"]
  • Annotated[str, Field(min_length=1)]

これらは、必ずしも新しい BaseModel を作るほどではない。
しかし、型として正しいかは検証したい。

このときに使えるのが TypeAdapter である。

ただし、TypeAdapterBaseModelAnnotated の代替ではない。
それぞれ役割が違う。

この記事では、TypeAdapter を単体で見るのではなく、BaseModelAnnotatedTypeAdapter の責務分離として整理する。

責務分離

FastAPI / Pydanticの入力検証を理解するときは、次の3つを分けて考えると分かりやすい。

BaseModel

BaseModelは下記を持つ。

  • 名前のあるデータ構造を定義する
  • データ構造の検証もできる

たとえば、ユーザー作成リクエストのように、複数のフィールドを持つ入力データは BaseModel で表現する。

BaseModel
from pydantic import BaseModel
# データモデル定義
class UserCreate(BaseModel):
name: str
age: int
# インスタンス生成と同時に検証する
user = UserCreate(name="John Doe", age=30)

Annotated

Annotatedは下記を持つ。

  • 制約付きの型を作る(型に制約やメタデータを付ける)
  • 型定義のみであり、それ自体が値を検証するわけではない

たとえばname に「1文字以上20文字以下」という制約を付けたい場合は、Annotated を使って型にルールを付けられる。

Annotated
from typing import Annotated
from pydantic import Field
# UserNameという制約付きの型を定義する
UserName = Annotated[str, Field(min_length=1, max_length=20)]

Annotatedは、あくまで型定義のみ

Annotatedに関しては下記記事で詳しくまとめている

TypeAdapter

TypeAdapterは下記を持つ。

  • 既存の型を受け取って、その型に対する検証器を作る
  • 型そのものを新しく作るものではない
TypeAdapter
from pydantic import TypeAdapter
# AnnotatedでUserNameという制約付きの型を定義する
UserName = Annotated[str, Field(min_length=1, max_length=20)]
# UserName型を検証するTypeAdapterを定義
user_name_adapter = TypeAdapter(UserName)
# 検証を行う
name = user_name_adapter.validate_python("taro")

この整理を先に持っておくと、TypeAdapter の位置づけが分かりやすくなる。

TypeAdapterとは

TypeAdapterについて詳しく解説する

TypeAdapter は、任意の型に対してPydanticの検証機能を使うための仕組みである。

Pydantic公式ドキュメントでは、

  • TypeAdapterBaseModel を作らずに型の検証、シリアライズ、JSON Schema生成を行うための機能

として説明されている。

TypeAdapterの基本構文

基本構文は次の通りである。

基本構文
from pydantic import TypeAdapter
adapter = TypeAdapter(検証したい型)
result = adapter.validate_python(入力値)

たとえば、dict[str, int] を検証する場合はこう書く。

検証1
from pydantic import TypeAdapter
adapter = TypeAdapter(dict[str, int])
data = adapter.validate_python({
"apple": "100",
"banana": 200,
})
print(data)
# {'apple': 100, 'banana': 200}
  • "100" は文字列だが、int に変換される。

バリデーションエラーにしたい場合

検証2
from pydantic import TypeAdapter
adapter = TypeAdapter(dict[str, int])
try:
data = adapter.validate_python({
"apple": "100",
"banana": 200,
},strict=True)
except ValidationError as e:
print(e)

Pydanticはデフォルトでは、変換可能な値を目的の型に変換する。
厳密に型一致を求める場合は strict=True を使う。

TypeAdapterでよく使う関数

TypeAdapterで検証でよく使う関数として

  • validate_python
  • validate_json

がある

validate_pythonでPythonオブジェクトを検証する

validate_python() は、Pythonオブジェクトを検証するメソッドである。

validate_python
from pydantic import TypeAdapter
adapter = TypeAdapter(list[int])
values = adapter.validate_python(["1", "2", 3])
print(values)
# [1, 2, 3]

外部APIのレスポンスを json.loads() した後、DBから取得した値、Redisから復元した値、CSVから読み込んだ値など、すでにPythonオブジェクトになっている値を検証するときに使う。


validate_jsonでJSON文字列を検証する

validate_json() は、JSON文字列を直接検証するメソッドである。

validate_json
from pydantic import TypeAdapter
adapter = TypeAdapter(list[int])
values = adapter.validate_json('[1, "2", 3]')
print(values)
# [1, 2, 3]

json.loads() してから validate_python() に渡すのではなく、JSON文字列をそのままPydanticで検証できる。
Redis、メッセージキュー、ファイルなどからJSON文字列を取得する場合に使いやすい。

AnnotatedとTypeAdapterの合わせ技

AnnotatedTypeAdapter は組み合わせると使いやすい。

Annotated で制約付きの型を定義し、その型を TypeAdapter で検証する。

py
from typing import Annotated
from pydantic import Field, TypeAdapter
# 型と制約定義
UserName = Annotated[str, Field(min_length=1, max_length=20)]
# 検証機作成
user_name_adapter = TypeAdapter(UserName)
# 検証
name = user_name_adapter.validate_python("taro")
print(name)
# taro

この使い方のメリットは、同じ制約を複数の場所で使い回せることである。

たとえば、FastAPIのリクエストモデルでは UserName を型として使う。

リクエストモデル
from pydantic import BaseModel
class UserCreate(BaseModel):
name: UserName

一方で、API以外の場所では TypeAdapter(UserName) を使って検証する。

それ以外
def validate_user_name(value: str) -> UserName:
return user_name_adapter.validate_python(value)

これにより、複数個所で同じルールを使い回せる。

  • Annotated はルールを定義する。
  • TypeAdapter はそのルールをAPI外でも使えるようにする。

この組み合わせは、実務でかなり使いやすい。

FastAPIでTypeAdapterを使う場面

FastAPIでは、リクエストボディやクエリパラメータの検証は基本的に自動で行われる。

そのため、エンドポイントの引数で完結するなら TypeAdapter を使う必要は少ない。

TypeAdapter が役立つのは、FastAPIの自動検証が届かない場所である。

  • 外部APIのレスポンス
  • DBやRedisから取得した値
  • JSON文字列
  • CSVやファイル取り込み
  • バッチ処理
  • サービス層の入口

つまり、FastAPIにおける TypeAdapter は、リクエストモデルの代替ではない。
アプリ内部や外部境界で、Pydanticの検証を使うための道具である。

実務例1: 外部APIレスポンスを検証する

外部APIから次のようなデータが返るとする。

外部APIレスポンス
[
{"id": 1, "name": "apple", "price": 120},
{"id": 2, "name": "banana", "price": "180"}
]

これをアプリ内部で使う前に検証したい。

検証関数
from pydantic import BaseModel, TypeAdapter
# データモデル定義
class Product(BaseModel):
id: int
name: str
price: int
# 検証機作成
products_adapter = TypeAdapter(list[Product])
def parse_products(response_data: object) -> list[Product]:
# 検証とインスタンス生成
return products_adapter.validate_python(response_data)
  • TypeAdapterを使って外部APIのレスポンスを検証する関数を定義
検証関数使用例
raw_data = [
{"id": 1, "name": "apple", "price": 120},
{"id": 2, "name": "banana", "price": "180"},
]
products = parse_products(raw_data)
print(products)
# [Product(id=1, name='apple', price=120), Product(id=2, name='banana', price=180)]
  • 外部APIのレスポンスを検証ができる
  • price"180" は文字列だが、int に変換される。

このケースでは、list[Product] そのものを検証したい。
そのため、次のようなラッパーモデルを作らなくてもよい。

ラッパーモデル
class ProductList(BaseModel):
products: list[Product]

もちろん、API仕様として products というキーを持つレスポンスを表現したいなら、ラッパーモデルを作る意味はある。
しかし、単に配列そのものを検証したいだけなら、TypeAdapter(list[Product]) の方が自然である。

実務例2: RedisやDBから取得したJSONを検証する

RedisやDBにJSON文字列を保存している場合、読み出した値はただの文字列である。

検証関数
from pydantic import BaseModel, TypeAdapter
class UserSession(BaseModel):
user_id: int
roles: list[str]
session_adapter = TypeAdapter(UserSession)
def load_session(json_text: str) -> UserSession:
return session_adapter.validate_json(json_text)
  • 検証関数を定義
検証関数使用例
json_text = '{"user_id": "123", "roles": ["admin", "editor"]}'
session = load_session(json_text)
print(session)
# user_id=123 roles=['admin', 'editor']
  • 検証を実行

user_id はJSON上では文字列だが、UserSession の定義に従って int に変換される。
Redis、DB、メッセージキュー、ファイルなど、アプリの外から入ってくる値は型が崩れている可能性がある。

このような外部境界で TypeAdapter を使うと、アプリ内部に値を入れる前に型を整えられる。

実務例3: カンマ区切りのクエリをlist[int]に変換する

既存APIやフロントエンド都合で、次のようなクエリパラメータを受けたい場合がある。

リクエスト
GET /items?ids=1,2,3

FastAPIで list[int] のクエリパラメータを普通に使う場合は、一般的には次のような形式になる。

リクエスト
GET /items?ids=1&ids=2&ids=3

しかし、1,2,3 のようなカンマ区切りを受ける必要があるなら、DependsTypeAdapter を組み合わせられる。

検証関数
from fastapi import Depends, FastAPI, Query
from pydantic import TypeAdapter
app = FastAPI()
ids_adapter = TypeAdapter(list[int])
def get_ids(ids: str = Query("")) -> list[int]:
if not ids:
return []
return ids_adapter.validate_python(ids.split(","))
@app.get("/items")
def list_items(ids: list[int] = Depends(get_ids)):
return {"ids": ids}

レスポンスは下記になる

レスポンス
{
"ids": [1, 2, 3]
}

ids.split(",") の結果は ["1", "2", "3"] である。

それを TypeAdapter(list[int]) で検証することで、list[int] に変換している。
ただし、この例は「既存仕様に合わせるための実装」である。
新規APIであれば、FastAPIの標準的なクエリ表現に寄せた方がよい場合もある。

実務例4: Annotatedで作った制約付き型を再利用する

AnnotatedTypeAdapter の組み合わせは、制約付き型を再利用したいときに便利である。

検証関数
from typing import Annotated
from pydantic import BaseModel, Field, TypeAdapter
# 型・制約定義
UserName = Annotated[str, Field(min_length=1, max_length=20)]
# 検証機定義
user_name_adapter = TypeAdapter(UserName)
# データモデル定義
class UserCreate(BaseModel):
name: UserName
def validate_user_name(value: str) -> UserName:
return user_name_adapter.validate_python(value)

これにより、APIでもAPI以外でも同じ UserName のルールを使える。

API以外での使用例
name = validate_user_name("taro")
print(name)
# taro

annotatedで定義した型を

  • データモデル
  • TypeAdapter

で再利用することでバリデーションルールが分散しにくい。

  • APIリクエストではUserNameを使う
  • バッチ処理ではTypeAdapter(UserName)を使う
  • CLI引数でもTypeAdapter(UserName)を使う

で同じルールで検証できる

TypeAdapterを使わない方がよい場面

TypeAdapter は便利だが、何でも TypeAdapter にすればよいわけではない。
リクエストボディ全体を表現したいなら、通常は BaseModel を使う方がよい。

データモデル
from pydantic import BaseModel
class UserCreate(BaseModel):
name: str
age: int

これは UserCreate という意味のある入力データである。
このような場合に、あえて dict[str, object]TypeAdapter で検証するより、BaseModel として定義した方が分かりやすい。

  • リクエストボディ全体
  • レスポンス全体
  • 業務上の意味を持つデータ
  • 複数フィールドを持つDTO
  • OpenAPIに明示したい構造

このようなものは、BaseModel の方が向いている。

一方で、次のようなものは TypeAdapter が向いている。

  • list[int]
  • list[Model]
  • dict[str, Model]
  • Literal["asc", "desc"]
  • Annotatedで作った制約付き型
  • JSON文字列から復元する一部の値

FastAPIの自動検証で足りるなら無理に使わない
エンドポイントの型アノテーションで済むなら、それが一番シンプルである。

まとめ

TypeAdapter は、Pydantic v2で BaseModelを作らずに任意の型を検証するための機能 である。

FastAPIで TypeAdapter を理解するときに重要なのは、次の2点である。

  1. Annotatedで作った型ルールを、TypeAdapterで再利用できる
  2. TypeAdapterは、主にFastAPIの自動検証が届かない場所で使う

Annotated で制約付きの型を作っておけば、FastAPIのリクエストモデルでは型として使える。
さらに、バッチ処理、CLI、外部データ取り込みなどでは TypeAdapter を使って同じルールで検証できる。

つまり、TypeAdapter はFastAPIのリクエストモデルを置き換えるものではない。
FastAPIの自動検証が届かない場所でも、同じ型ルールを使って値を検証するための仕組みである。

関連記事

新着記事

タグ一覧
top