Python

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.Serializerserializers.ModelSerializerがあります。ModelSerializerSerializerのサブクラスで、モデル定義からフィールド・基本バリデーション・create/updateを自動生成する点が違います。

比較軸 Serializer ModelSerializer
フィールド定義 すべて手書き モデルから自動生成
create/update 自分で実装 自動実装(オーバーライド可)
向く用途 モデルに紐づかない入出力(検索条件・集計結果など) モデルのCRUD

結論として、モデルの入出力ならModelSerializerを選びます。素のSerializerは「ログインのユーザー名・パスワードを受け取るだけ」「複数モデルを集計した独自レスポンス」など、単一モデルに対応しない場面に限定するのが実務的です。

DRF Serializerの基本的な使い方(導入からModelSerializer定義まで)

ここでは最短でSerializerを動かす流れを示します。

DRFのインストールと設定

DRFはPyPIからインストールし、settings.pyINSTALLED_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に渡し、.dataResponseに載せれば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_onlywrite_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を書かずに済み実装が単純になります。

N+1問題を避けるselect_related/prefetch_related

関連を展開する一覧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に分ける必要があります。

関連記事

資料請求

RELATED POSTS 関連記事