この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:5
ページ更新者:T
更新日時:2026-06-11 07:12:00

タイトル: モデルのjson化とレスポンス
SEOタイトル: 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)
# {&quot;d&quot;: &quot;2025-01-15&quot;, &quot;p&quot;: &quot;123.45&quot;, &quot;u&quot;: &quot;...&quot;}

# 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']

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

# 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,
}

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

# 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 経由が圧倒的に安全・速いです。