24.

Django モデルの JSON 化とレスポンス完全ガイド(JsonResponse / serializers / DRF)

編集
この記事の要点
  • 最も簡単: JsonResponse({"id": obj.id, "name": obj.name}) で辞書を直接返す
  • QuerySet 一括: django.core.serializers.serialize("json", qs)
  • 本命は DRF: ModelSerializer でフィールド宣言的に、@api_view + Response で返却
  • Date / Decimal / UUID は標準 json モジュールで失敗 → DjangoJSONEncoder 必須
  • ネスト関係は DRF の depth / nested serializer / SerializerMethodField で表現

4 つのアプローチ

方法用途長所短所
1. JsonResponse 直接小規模 API、単純レコード追加依存ゼロ、最速フィールド指定が手動
2. django.core.serializersQuerySet 丸ごと標準同梱独特なラップ形式
3. DRF ModelSerializer★ 本格 API宣言的、バリデーション付きパッケージ追加
4. 自作 to_dict()カスタム整形柔軟メンテ負担

方法1: JsonResponse で直接返す

from django.http import JsonResponse
from .models import Book

def book_detail(request, pk):
    book = Book.objects.get(pk=pk)
    return JsonResponse({
        'id':           book.id,
        'title':        book.title,
        'isbn':         book.isbn,
        'published_at': book.published_at.isoformat(),   # date → ISO 文字列
        'author':       {
            'id':   book.author.id,
            'name': book.author.name,
        },
    })

# QuerySet をリストで返す
def book_list(request):
    qs = Book.objects.select_related('author').all()
    data = [{
        'id':    b.id,
        'title': b.title,
        'author': b.author.name,
    } for b in qs]
    return JsonResponse({'count': len(data), 'results': data})
    # ↑ safe=False が必要な場合は: JsonResponse(data, safe=False)

safe=False の話

JsonResponse はデフォルトで辞書のみ受け付けます。リストを直接返したい場合は safe=False を付けます:

return JsonResponse([{'a': 1}, {'b': 2}], safe=False)
# safe=True (既定) で list を渡すと TypeError 発生

# JSON Hijacking 対策で禁止されている、ラップ推奨:
return JsonResponse({'data': [{'a': 1}, {'b': 2}]})

方法2: django.core.serializers

from django.core import serializers
from django.http import HttpResponse
from .models import Book

def book_list_json(request):
    qs = Book.objects.all()
    data = serializers.serialize('json', qs, fields=('title', 'isbn'))
    return HttpResponse(data, content_type='application/json')

# 出力例
# [
#   {"model": "library.book", "pk": 1,
#    "fields": {"title": "Python入門", "isbn": "9784..."}},
#   ...
# ]

独自のフォーマット(model / pk / fields)でラップされるため、フロント側が普通の JSON を期待していると扱いにくい点に注意。fixture 形式に近い。

方法3: Django REST Framework(推奨)

pip install djangorestframework

# settings.py
INSTALLED_APPS += ['rest_framework']

ModelSerializer

# serializers.py
from rest_framework import serializers
from .models import Book, Author

class AuthorSerializer(serializers.ModelSerializer):
    class Meta:
        model  = Author
        fields = ['id', 'name', 'birth_date']

class BookSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)        # ネスト表示
    author_id = serializers.PrimaryKeyRelatedField(
        queryset=Author.objects.all(), source='author', write_only=True
    )
    # 計算項目
    is_recent = serializers.SerializerMethodField()

    class Meta:
        model  = Book
        fields = ['id', 'title', 'isbn', 'published_at',
                  'author', 'author_id', 'is_recent']
        read_only_fields = ['id']

    def get_is_recent(self, obj):
        from datetime import date
        return (date.today() - obj.published_at).days < 90


# views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .serializers import BookSerializer

@api_view(['GET'])
def book_list(request):
    qs = Book.objects.select_related('author').all()
    ser = BookSerializer(qs, many=True)
    return Response(ser.data)

@api_view(['POST'])
def book_create(request):
    ser = BookSerializer(data=request.data)
    ser.is_valid(raise_exception=True)
    ser.save()
    return Response(ser.data, status=201)

ViewSet + Router でフル CRUD

from rest_framework import viewsets
from rest_framework.routers import DefaultRouter

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.select_related('author')
    serializer_class = BookSerializer

router = DefaultRouter()
router.register(r'books', BookViewSet)
urlpatterns = router.urls
# → /books/ (GET, POST), /books/<id>/ (GET, PUT, PATCH, DELETE) 自動生成

Date / UUID / Decimal の扱い

Python 標準の json.dumps()date / datetime / Decimal / UUID をそのままシリアライズできません:

import json
from datetime import date
from decimal import Decimal
from uuid import uuid4

data = {'d': date.today(), 'p': Decimal('123.45'), 'u': uuid4()}

# ❌ TypeError: Object of type date is not JSON serializable
json.dumps(data)

# ✅ Django 標準のエンコーダを使う
from django.core.serializers.json import DjangoJSONEncoder
json.dumps(data, cls=DjangoJSONEncoder)
# {"d": "2025-01-15", "p": "123.45", "u": "..."}

# JsonResponse は内部で DjangoJSONEncoder を使うので OK
from django.http import JsonResponse
JsonResponse(data)   # 動く

各型の出力フォーマット

Python 型JSON 出力
date"2025-01-15"
datetime"2025-01-15T10:00:00+09:00"
time"10:00:00"
timedelta"3 12:00:00"
Decimal"123.45"(文字列)
UUID"550e8400-e29b-41d4-a716-446655440000"
Promise (lazy)str() 評価後の文字列

ネスト関係の扱い

# Author → Book[]
class AuthorWithBooksSerializer(serializers.ModelSerializer):
    books = BookSerializer(many=True, read_only=True)     # related_name='books' で逆参照

    class Meta:
        model  = Author
        fields = ['id', 'name', 'books']

# 出力
# {
#   "id": 1, "name": "村上春樹",
#   "books": [
#     {"id": 11, "title": "...", "isbn": "..."},
#     {"id": 12, "title": "...", "isbn": "..."}
#   ]
# }

# depth で簡易ネスト(書き込みは不可)
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = '__all__'
        depth = 1    # ForeignKey が自動ネスト

SerializerMethodField で計算項目

class BookSerializer(serializers.ModelSerializer):
    days_since_published = serializers.SerializerMethodField()
    author_name = serializers.CharField(source='author.name', read_only=True)
    full_url = serializers.SerializerMethodField()

    class Meta:
        model  = Book
        fields = ['id', 'title', 'author_name',
                  'days_since_published', 'full_url']

    def get_days_since_published(self, obj):
        from datetime import date
        return (date.today() - obj.published_at).days

    def get_full_url(self, obj):
        request = self.context.get('request')
        return request.build_absolute_uri(f'/books/{obj.id}/') if request else None

パフォーマンス: N+1 を避ける

# ❌ N+1: author を毎回 SELECT
qs = Book.objects.all()
BookSerializer(qs, many=True).data
# SELECT * FROM book;
# SELECT * FROM author WHERE id=1;   ← N 回
# SELECT * FROM author WHERE id=2;
# ...

# ✅ select_related (1:1, ForeignKey)
qs = Book.objects.select_related('author')

# ✅ prefetch_related (1:N, M:N)
qs = Author.objects.prefetch_related('books')

# ✅ さらに細かく
qs = Author.objects.prefetch_related(
    Prefetch('books', queryset=Book.objects.filter(is_published=True))
)

ページネーション

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 20,
}

# 出力
# {
#   "count": 145,
#   "next": "http://localhost:8000/books/?page=2",
#   "previous": null,
#   "results": [ ... ]
# }

# Cursor 方式(大量データ向け)
class BookCursor(CursorPagination):
    ordering = '-published_at'
    page_size = 50

カスタム to_dict() アプローチ

# models.py に直接生やす
class Book(models.Model):
    title = models.CharField(max_length=200)
    isbn  = models.CharField(max_length=13)

    def to_dict(self, *, with_author=False):
        d = {
            'id':    self.id,
            'title': self.title,
            'isbn':  self.isbn,
        }
        if with_author:
            d['author'] = self.author.to_dict()
        return d

# views.py
def book_list(request):
    qs = Book.objects.select_related('author')
    return JsonResponse({'results': [b.to_dict(with_author=True) for b in qs]})

FAQ

Q: JsonResponseHttpResponse の違いは?
A: JsonResponse は内部で json.dumps(cls=DjangoJSONEncoder) を呼び、Content-Type: application/json を自動付与します。素の HttpResponse だと両方手動。

Q: 日付のタイムゾーンが UTC で返ってしまう
A: settings.pyTIME_ZONEUSE_TZ=True を確認。表示用に Asia/Tokyo へ変換するには django.utils.timezone.localtime()

Q: DRF を入れずに JWT 認証だけ追加したい
A: PyJWT で自前実装が可能ですが、djangorestframework-simplejwt 経由が圧倒的に安全・速いです。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 環境構築とプロジェクト/アプリの作成
  2. MVC(MVT)のそれぞれの使い方と説明
  3. データベースへの接続と操作
  4. Django Administration
  5. git管理
  6. エラー一覧
  7. バージョンの確認方法
  8. ログ出力方法
  9. SQLのログ出力方法
  10. ログのローテート設定
  11. settings.pyの定数にアクセスする方法
  12. 本番環境へのインストールとアプリのデプロイ(apache編)
  13. 本番環境へのインストールとアプリのデプロイ(nginx編)
  14. djangoアプリの本番の開始URLを変更する
  15. 静的(static)ファイルの置き場所と読み込み(画像、css、js )
  16. CSRFトークンをAjaxで使用する方法
  17. ajaxの使用例(POST編)
  18. ファイルのアップロードとファイルの名前
  19. クイックスタート/チュートリアル
  20. ログイン機能
  21. テンプレート側のログイン判定
  22. ビュー側のログイン判定
  23. 管理者ユーザーの作成/判定と管理画面
  24. モデルのjson化とレスポンス
  25. runserverでポートを指定する方法
  26. cronによるバッチ実行
  27. テンプレートで利用する共通のcontextを定義する方法
  28. プログラムが本番サーバーで反映されない場合の対処法
  29. APIの作成
  30. cron用コマンド・ファイルの作成

最近更新/作成されたページ