Django REST FrameworkのSerializer(シリアライザ)とは?使い方・フィールド・バリデーション・ネストを解説
Django REST Framework(DRF)のSerializer(シリアライザ)は、DjangoモデルのインスタンスとJSONなどの外部表現を相互に変換するコンポーネントです。モデル→JSONの「シリアライズ」だけでなく、リクエストで受け取ったJSON→モデルの「デシリアライズ」と入力検証も担うため、Web APIを作るうえで中心になります。この記事では、Serializerの役割から、ModelSerializerの基本、フィールドのオプション、バリデーション、そして多くの人がつまずくリレーション(ネスト)とN+1対策までを、動くコード例で順に解説します。
まとめ:DRF Serializerの要点
- Serializerは双方向の変換器。モデル→JSON(シリアライズ)と、JSON→モデル+検証(デシリアライズ)の両方を担う。
- 基本は
ModelSerializer。モデルを指定してfieldsを書くだけで、フィールド定義・基本バリデーション・create/updateが自動生成される。素のSerializerはモデルに紐づかない入出力で使う。 - フィールドは
read_only/write_only/source/requiredで挙動を制御する。読み取り専用か書き込み専用か、参照元の属性は何かを明示する。 - 検証は
validate_<field>()(フィールド単位)とvalidate()(オブジェクト全体)で書き、ビュー側はis_valid()→save()の順で保存する。 - モデルに無い計算値は
SerializerMethodFieldで返せるが、読み取り専用でデシリアライズ(保存)には使えない。 - リレーションはネストで表現でき、書き込みには
create/updateのオーバーライドが必要。一覧取得ではselect_related/prefetch_relatedでN+1を避ける。
Serializer(シリアライザ)とは?DRFにおける役割
SerializerはDRFの中核で、「アプリ内部のデータ(モデルインスタンスやクエリセット)」と「APIがやり取りする外部形式(JSONなど)」の橋渡しをします。DRF全体の位置づけやWeb APIの作り方はDjango REST Frameworkとは?その特徴と利点を徹底解説で扱っているため、本記事はSerializerに絞って説明します。
シリアライズとデシリアライズ:2方向の変換
Serializerが担う変換は2方向あります。役割を混同すると、後述のバリデーションやネストでつまずきます。
| 方向 | 呼び名 | 入力 | 出力 | 入口API |
|---|---|---|---|---|
| モデル→JSON | シリアライズ | モデル/クエリセット | Python基本型(→JSON) | serializer.data |
| JSON→モデル | デシリアライズ | リクエストのデータ | 検証済みデータ/保存 | is_valid() → save() |
シリアライズはインスタンスを渡して.dataを読むだけです。many=Trueを付けるとクエリセットをそのまま配列に変換できます。
serializer = PostSerializer(post)
serializer.data # {'title': '...', 'content': '...'}
serializer = PostSerializer(posts, many=True)
serializer.data # [{...}, {...}]
デシリアライズはdata=にリクエストの値を渡し、is_valid()で検証してからsave()します。この検証こそSerializerが「単なる変換器」以上の価値を持つ部分です。
SerializerとModelSerializerの違いと使い分け
DRFにはserializers.Serializerとserializers.ModelSerializerがあります。ModelSerializerはSerializerのサブクラスで、モデル定義からフィールド・基本バリデーション・create/updateを自動生成する点が違います。
| 比較軸 | Serializer | ModelSerializer |
|---|---|---|
| フィールド定義 | すべて手書き | モデルから自動生成 |
| create/update | 自分で実装 | 自動実装(オーバーライド可) |
| 向く用途 | モデルに紐づかない入出力(検索条件・集計結果など) | モデルのCRUD |
結論として、モデルの入出力ならModelSerializerを選びます。素のSerializerは「ログインのユーザー名・パスワードを受け取るだけ」「複数モデルを集計した独自レスポンス」など、単一モデルに対応しない場面に限定するのが実務的です。
DRF Serializerの基本的な使い方(導入からModelSerializer定義まで)
ここでは最短でSerializerを動かす流れを示します。
DRFのインストールと設定
DRFはPyPIからインストールし、settings.pyのINSTALLED_APPSに登録します。DRFは3.x系が長く使われており、Serializerの基本APIはこの系列で安定していますが、最新版と対応Djangoバージョンは公式サイトで確認してください。
pip install djangorestframework
INSTALLED_APPS = [
# ...
'rest_framework',
]
ModelSerializerでの定義と基本形
serializers.pyを作り、Metaに対象モデルと公開するfieldsを書きます。fields = '__all__'で全フィールドを対象にできますが、意図しない列の露出を防ぐため、実務では必要なフィールドを明示列挙するのが安全です。
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'content', 'author', 'created_at']
シリアライズ結果をResponseで返す
ビューでインスタンスやクエリセットをSerializerに渡し、.dataをResponseに載せればJSONで返せます。JSONRendererを明示的に呼ぶ必要はなく、Responseがレンダリングを担います。
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Post
from .serializers import PostSerializer
class PostListView(APIView):
def get(self, request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
Serializerフィールドの種類とオプション
Serializerのフィールドは、値の型(CharFieldなど)と挙動オプション(読み取り専用かなど)の2要素で決まります。ModelSerializerはモデルの列から型を推論しますが、オプションはこちらで指定して制御します。
Serializerの主なフィールド型
| フィールド | 対応する主な値 |
|---|---|
CharField |
文字列 |
IntegerField / FloatField |
数値 |
BooleanField |
真偽値 |
DateTimeField / DateField |
日時・日付 |
EmailField / URLField |
形式チェック付き文字列 |
SerializerMethodField |
メソッドで算出した読み取り専用値 |
read_only / write_only / required / source:よく使うオプション
フィールドの挙動を決める代表的なオプションです。特にread_onlyとwrite_onlyは、どちらの変換方向で使うかを明示するもので、意味を取り違えると「更新したのに反映されない」「レスポンスにパスワードが出てしまう」といった事故につながります。
| オプション | 効果 | 典型用途 |
|---|---|---|
read_only=True |
出力に含めるが入力では無視 | id・作成日時 |
write_only=True |
入力では受け取るが出力に出さない | パスワード |
required=False |
入力で省略を許可 | 任意項目 |
source='attr' |
参照するモデル属性名を指定 | 属性名とAPI名を変える |
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
joined = serializers.DateTimeField(source='date_joined', read_only=True)
class Meta:
model = User
fields = ['id', 'username', 'password', 'joined']
この例では、passwordはリクエストで受け取れてもレスポンスには出ず、joinedはモデルのdate_joinedを別名で読み取り専用に公開しています。なおUserのパスワードを保存する際は、後述のとおり平文のまま保存されないようset_password()によるハッシュ化が必要です。
バリデーション(入力データの検証)の実装
デシリアライズ時の入力検証はSerializerの主要な役割です。検証はフィールド単位とオブジェクト全体の2階層で書き分けます。
フィールド単位のvalidate_<field>()
1つのフィールドだけを検証するには、validate_<フィールド名>メソッドを定義します。問題があればserializers.ValidationErrorを送出し、検証を通れば値を返します。
class SignupSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = User
fields = ['username', 'email', 'password']
def validate_email(self, value):
if not value.endswith('@example.com'):
raise serializers.ValidationError('社内ドメインのメールアドレスを指定してください')
return value
オブジェクト全体のvalidate()とvalidators
複数フィールドをまたぐ検証(パスワードと確認用の一致、開始日と終了日の前後関係など)はvalidate()で行います。引数attrsには検証済みの全フィールドが入ります。
def validate(self, attrs):
if attrs['username'] in attrs.get('password', ''):
raise serializers.ValidationError('パスワードにユーザー名を含めないでください')
return attrs
単純な条件(最大長・数値範囲・一意制約)はメソッドを書かず、フィールドのvalidators引数やモデル側の制約に任せると重複が減ります。
デシリアライズして保存する流れ(is_valid → save)
ビュー側では、受け取ったデータをdata=に渡し、is_valid()で検証してからsave()します。is_valid()を呼ぶ前に.dataや.validated_dataを参照するとエラーになるため、順序を守ります。検証に失敗したらserializer.errorsをそのまま返せます。
from rest_framework import status
from rest_framework.response import Response
class SignupView(APIView):
def post(self, request):
serializer = SignupSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
ただしUserのようにパスワードを持つモデルでは、ModelSerializerの既定のcreate()がUser.objects.create(**validated_data)を実行し、パスワードをハッシュ化せず平文で保存してしまいます(作成したユーザーはログインできません)。パスワードを扱うときはcreate()をオーバーライドし、create_user()(内部でset_password()を呼ぶ)を使ってください。
def create(self, validated_data):
return User.objects.create_user(
username=validated_data['username'],
email=validated_data.get('email', ''),
password=validated_data['password'],
)
モデルに無い値をレスポンスに含める(SerializerMethodField)
税込価格やフルネームのように、モデルに列として存在しない値をレスポンスへ加えたいときはSerializerMethodFieldを使います。
SerializerMethodFieldで計算・整形した値を返す
SerializerMethodFieldは、既定でget_<フィールド名>という名前のメソッドを呼び、その戻り値を出力します。メソッドは対象オブジェクトを引数に受け取ります。
class ProductSerializer(serializers.ModelSerializer):
price_with_tax = serializers.SerializerMethodField()
class Meta:
model = Product
fields = ['name', 'price', 'price_with_tax']
def get_price_with_tax(self, obj):
return int(obj.price * 1.1)
SerializerMethodFieldを使うべきでない場面
便利な反面、乱用するとAPIの品質を落とします。次の性質を理解して使いどころを絞るべきです。
- 読み取り専用:
SerializerMethodFieldは常に読み取り専用で、デシリアライズ(保存)には一切関与しません。入力も受け取りたい値には使えません。 - 単なる別名なら不要:モデル属性をそのまま/別名で出すだけなら、メソッドを書かず
source付きの型フィールド(例serializers.CharField(source='...'))の方が簡潔で、型情報も保てます。 - N+1を招きやすい:メソッド内で
obj.related.count()のように関連を都度参照すると、一覧取得でクエリが件数分発行されます。集計は後述のprefetch_relatedやアノテーションで先に用意します。
つまり、SerializerMethodFieldは「出力専用で、計算・整形が必要な値」に限定し、双方向の値や単純な別名には使わないのが判断基準です。
リレーションを持つデータのシリアライズ(ネスト/association)
投稿と著者、投稿とタグのように関連を持つデータは、関連の表現方法を選ぶ必要があります。ここがSerializerで最も設計判断が要る部分です。
PrimaryKeyRelatedFieldとネストシリアライザの違い
関連の出し方は大きく2通りです。IDだけ返すか、関連オブジェクトの中身まで展開するかで、レスポンスサイズと必要なクエリが変わります。
| 方法 | 出力 | 向く場面 |
|---|---|---|
PrimaryKeyRelatedField |
関連レコードのID | 一覧・軽量なレスポンス |
| ネストシリアライザ | 関連レコードの中身 | 詳細画面・関連情報を同時に見せる |
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name']
class PostSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True) # 中身を展開
tag_ids = serializers.PrimaryKeyRelatedField(
many=True, read_only=True, source='tags') # IDのみ
class Meta:
model = Post
fields = ['id', 'title', 'author', 'tag_ids']
書き込み可能なネストシリアライザ(create/update)の実装
ネストシリアライザをそのまま置くと読み取りはできますが、その状態でPOST/PUTしても関連は保存されません。DRFは入れ子の書き込みを自動処理しないためで、実装を誤りやすい部分です。書き込みを許すにはcreate(と必要ならupdate)を自分で実装します。
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['name']
class PostSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
class Meta:
model = Post
fields = ['id', 'title', 'tags']
def create(self, validated_data):
tags_data = validated_data.pop('tags')
post = Post.objects.create(**validated_data)
for tag_data in tags_data:
tag, _ = Tag.objects.get_or_create(**tag_data)
post.tags.add(tag)
return post
ネストを読み取り専用でよいならread_only=Trueを付け、書き込みは別のPrimaryKeyRelatedFieldでIDを受ける設計にすると、createを書かずに済み実装が単純になります。
関連を展開する一覧APIは、対策しないと1件ごとに関連取得のクエリが走るN+1問題を起こします。ForeignKey系はselect_related、ManyToMany・逆参照はprefetch_relatedでクエリセット側にまとめて取得させます。
posts = Post.objects.select_related('author').prefetch_related('tags')
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
N+1の見つけ方や他フレームワークとの比較はDjangoとRailsにおけるN+1問題解決法の比較と考察で詳しく扱っています。
よくある質問(FAQ)
DjangoのSerializer(シリアライザ)とは何ですか?
DjangoモデルなどのデータとJSONなど外部形式を相互変換するDRFのコンポーネントです。モデル→JSONの出力(シリアライズ)と、受け取ったデータの検証・モデルへの変換(デシリアライズ)の両方を担います。
SerializerとModelSerializerはどちらを使うべきですか?
モデルの入出力ならModelSerializerです。フィールド・基本バリデーション・create/updateが自動生成され記述量が減ります。単一モデルに対応しない入出力(ログイン入力や集計結果など)のときだけ素のSerializerを使います。
read_onlyとwrite_onlyの違いは何ですか?
read_only=Trueは出力にだけ含め入力では無視するフィールド(idや作成日時向け)、write_only=Trueは入力でだけ受け取り出力には出さないフィールド(パスワード向け)です。使う変換方向が逆になります。
SerializerMethodFieldはデシリアライズ(保存)に使えますか?
使えません。SerializerMethodFieldは常に読み取り専用で、出力にのみ関与します。入力も受け取りたい値は通常のフィールドやカスタムフィールドで定義してください。
ネストしたSerializerでPOST/PUTすると保存できないのはなぜですか?
DRFは入れ子の書き込みを自動処理しないためです。書き込みを許すにはcreate(更新ならupdate)をオーバーライドして関連の保存処理を自分で書くか、書き込みをID受け取りのPrimaryKeyRelatedFieldに分ける必要があります。