장고 시작하기 27. 다양한 JSON 구조로 응답하기

장고의 serializer는 정말 간단하게 데이터를 직렬화 해서 전송할 수 있습니다. 뿐만 아니라 다양한 방법을 통해서 데이터의 내용 및 구조를 변경 할 수도 있습니다. 결과적으로 다양한 JSON 구조로 API 응답을 할 수 있게 해주는거죠.

이번 포스트에서는 DRF에서 다양한 JSON 구조로 응답할 수 있게 해주는 Serializer Method Fields, serializers의 source, to_representation() 에 대해서 공부한 내용을 정리해보았습니다.

가공된 데이터 추가 1 : Serializer Method Fields

Serializer Method Fields는 개체의 직렬화된 표현에 사용자 정의 데이터나 계산된 값을 포함하는 유연한 방법을 제공합니다.

Serializer Method Fields로 선언한 필드는 읽기 전용 필드로 정의되기 때문에 read_only=True 인자를 추가로 전달 할 필요는 없습니다.

일반 모델 필드와 달리 Serializer Method Fields는 특정 모델 속성에 연결되지 않습니다. 대신, serializer 클래스 자체에 정의된 메서드를 기반으로 합니다.

serializer를 사용하여 데이터를 직렬화할 때 serializer를 메서드 필드는 해당 메서드를 자동으로 호출하여 출력에 포함할 값을 계산합니다.

from django.contrib.auth.models import User
from django.utils.timezone import now
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):
    days_since_joined = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = '__all__'

    def get_days_since_joined(self, obj):
        return (now() - obj.date_joined).days

def get_days_since_joined(self, obj)

  • “get_”이 앞에 오는 필드 이름으로 메서드 이름을 지정합니다
  • obj 인자를 통해서 필드를 동적으로 변환할 수 있게 됩니다.

필드에는 get_days_since_joined에서 get_을 제외한 필드가 응답에 추가됩니다.

가공된 데이터 추가 2 : serializers의 source 활용

게시글 상세 조회만 하더라도 해당하는 댓글들과 그 수도 응답할 수 있도록 변경해야 한다고 가정해봅니다.

이때는 Article 시리얼라이져에 serializers.IntegerField를 추가 해줌으로써 간단히 이를 구현할 수 있습니다.

comments_count = serializers.IntegerField(source="comments.count", read_only=True)

  • source 인자로 개수를 세는 ORM인 .count을 전달해서 nested 데이터의 개수를 반환해줄 수 있습니다.
from rest_framework import serializers
from .models import Articles, Comments


class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comments
        fields = "__all__"
        read_only_fields = ("article",)


class ArticleSerializer(serializers.ModelSerializer):
    comments = CommentSerializer(many=True, read_only=True)
    comments_count = serializers.IntegerField(source="comments.count", read_only=True)
    class Meta:
        model = Articles
        fields = "__all__"

응답 JSON

{
    "id": 3,
    "comments": [
        {
            "id": 21,
            "content": "새로운 댓글을 추가합니다.",
            "created_at": "2024-04-24T14:37:00.958958+09:00",
            "updated_at": "2024-04-24T14:37:00.958984+09:00",
            "article": 3
        }
    ],
    "comments_count": 1,
    "title": "Choice standard forward.",
    "content": "Action central similar boy certainly. Certainly cost health scene exactly order. Prepare especially face specific than hundred top.",
    "created_at": "1972-05-01T00:40:37.736189+09:00",
    "updated_at": "1991-03-22T23:24:19.690504+09:00"
}

데이터 가공, 삭제, key값 변경 : to_representation()

API 구조에서 read_only만 시키고 싶은 것도 아니고 CRUD에서 아예 exclude 싶은 것도 아니고 API 응답에서만 제외시키고 싶을 수도 있습니다.

예를 들어서 위와 동일하게 게시글, 댓글 모델이 있다고 가정해봅니다.

시리얼라이져를 사용하면 댓글 모델의 Foreign Key 때문에 댓글 상세 API 에서 article_id가 저절로 추가 됩니다.

하지만 해당 내용은 사용하지 않기 때문에 빼고 싶다고 가정해 봅니다.

이때 사용할 수 있는 메소드가 to_representation() 입니다.

to_representation() 메서드는 DRF의 Serializer 클래스의 일부로 직렬화된 데이터의 표현을 API 응답 렌더링에 적합한 형식으로 변환하는 일을 담당합니다.

이 메서드를 재정의하면 개발자는 직렬화된 데이터가 렌더링되기 전에 구조를 수정할 수 있습니다

필드 이름을 바꾸거나, 새 필드를 추가하거나, 관련 데이터를 중첩하거나, 필요한 기타 변환을 수행할 수 있습니다.

필요 없는 필드를 삭제할 때는 pop() 메소드를 사용합니다.

from rest_framework import serializers
from .models import Articles, Comments


class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comments
        fields = "__all__"
        read_only_fields = ("article",)

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret.pop("article")
        return ret

...

API 응답 결과

응답에서 필드 삭제

위에서 언급했듯이 pop() 메소드를 사용합니다.

...
    def to_representation(self, instance):
        ret = super().to_representation(instance)
        ret.pop("article")
        return ret

응답에서 필드 이름 변경

...
    def to_representation(self, instance):
        # Customize the structure and rename fields
        rep = super().to_representation(instance)
        rep['product_name'] = rep.pop('name')
        return rep

응답에서 필드 추가

...
    def to_representation(self, instance):
        # Add a new field to the representation
        rep = super().to_representation(instance)
        rep['formatted_price'] = f"${rep['price']}"
        return rep

마치며

Nested serializer를 넘어서 이제는 필드값을 가공하여 추가하거나 수정, 제거 할 수도 있게 되었습니다…

API 응답 내용 및 구조 변경은 하나의 정답이 아니라 다양한 방법으로 이를 구현할 수 있는 것 같습니다.

이제 API 응답 결과를 마음대로 주무를 수 있게 된거죠…. 하하

물론 팀에 들어가게 되면 뭐든 회의를 통해서 결정 되겠지만…

아무튼 기분이 아주 좋습니다…

참고하면 좋은 글

DRF 필드 커스텀 공식 문서

Leave a Comment

목차