(DRF) Serialization & Serializer

duo2208·2022년 1월 28일
0

Django

목록 보기
14/23
post-thumbnail
post-custom-banner
🚀 (DRF API Guide) Serializers
직렬 변환기를 사용하면 쿼리 세트 및 모델 인스턴스와 같은 복잡한 데이터를 네이티브 python 데이터 유형으로 변환한 다음 JSON, XML 또는 다른 콘텐츠 유형으로 쉽게 렌더링할 수 있습니다. 직렬 변환기의 유용성 확장은 우리가 다루고자 하는 것입니다. 그러나 이는 사소한 문제가 아니며 몇 가지 심각한 설계 작업이 필요합니다.

직렬화 (Serialization) 란?


대부분의 서비스에는 통신이 필요하고, 어떤 프로토콜을쓰던 간에 컴포넌트 간 데이터 통신을 위해서는 프로그래밍 언어나 플랫폼에 독립적인 표준화된 포맷(문자열)이 필요합니다.

클라이언트가 객체를 문자열로 변환하여 서버로 데이터를 전송하는 것을 직렬화,
서버가 수신한 문자열을 다시 객체로 변환하여 활용하는 것을 비직렬화 라고 합니다.

보통의 웹에서는 요청에 대해 HTML 포맷 으로 응답하되, 요즘 대부분의 API 서버에서는 JSON 으로 인코딩 된 요청과 응답을 가집니다.



Django 의 직렬화


먼저 Django의 직렬화를 이해하면 DRF의 직렬화를 왜 쓰는가에 대한 이해가 쉬워집니다.

django.core.serializers.json.DjangoJSONEncoder

파이썬 기본 라이브러리를 사용하는 json.JSONEncoder 을 상속 받은 DjangoJSONEncoder 를 통해 다음과 같은 직렬화를 합니다. 이 변환기는 datetime.datetime, datetime.data 등등 의 몇가지 타입에 대한 Rule 을 제공하지만, 장고 타입(Model / Queryset)에 대해서는 직렬화 Rule 이 없습니다.

import json
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.auth import get_user_model

qs = get_user_model().objects.all()
json_string = json.dumps(qs, cls=DjangoJSONEncoder)	# 변환 Rule 적용
print(json_string)
TpyeError: Object of type 'User' is not JSON serializable

따라서 쿼리를 직렬화하려는 부분에서 위와 같은 TypeError 가 발생하게 됩니다. 이를 해결하기 위해 아래와 같은 방법을 사용합니다.


(1) 직접 파이썬 기본 객체로 변환하는 방법

각 데이터마다 직접 일일이 변환해주는 방법입니다. 하지만 이는 매우 번거로우니 직접 변환 Rule 을 따로 만듭니다.

data = [
	{'id': post.id, 'title': post.title, 'content': post.content}
 	for post in Post.objects.all()]
    
json.dumps(data, ensure_ascii=False).encode('utf8')

# ensure_ascii=False : 유니코드로 표현
# .encode('utf-8') : 특정 인코딩으로 표현

(2) 직접 변환 Rule을 지정하는 방법

기존에 제공해주는 타입이외에 몇가지 타입을 커스텀하여 추가합니다.

from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.query import QuerySet

# 커스텀 JSON Endoer를 정의
class MyJSONEncoder(DjangoJSONEncoder):
	def default(self, obj):
		""" QuerySet인 경우 tuple타입으로 변환한다. """
		if isinstance(obj, QuerySet):
			return tuple(obj)
		""" Post 모델 타입인 경우 사전타입으로 변환한다. """
 		elif isinstance(obj, Post):
  			return{'id': obj.id, 'title': obj.title, 'content': obj.content}
		""" as_dict() 메서드인 경우 호출해서 반환값을 리턴한다. """
  		elif hasattr(obj, 'as_dict'):
 			return obj.as_dict()
  		""" 3가지 타입 이외의 경우 DjangoJSONEncoder 를 사용한다. """
  		return super().default(obj)

data = Post.objects.all()

# 직렬화할 때, 직렬화를 수행해줄 JSON Encoder를 지정
json.dumps(data, clas=MYJSONENcoder, ensure_ascii=False)



DRF 의 직렬화


rest_framework.utils.encoders.JSONEncoder

DRF의 JSONEncoder 에서는 우리가 위에서 커스텀했던 속성들을 이미 지원하고 있습니다.

  • 장고의 DjangoJSONEncoder 를 상속받지 않고, json.JSONEncoder 상속을 통해 구현.
  • datetime.datetime / date / time / timedelta, decimal.Decimal, uuid.UUID, six,binary_type 를 대응한다.
  • __getitem__ 속성을 지원할 경우, dict(obj) 변환.
  • __iter__ 속성을 지원할 경우, tuple 변환.
  • QuerySet 타입일 경우, tuple 변환.
  • .tolist 속성을 지원할 경우, obj.tolist() 반환
  • Model 타입은 미지원.

rest_framework.renderer.JSONRenderer

json.dumps 에 대한 래핑클래스로 보다 편리한 직렬화를 지원하며, UTF8 인코딩도 추가로 수행합니다. 내부적으로 JSONEncoder 를 사용하므로 Model 타입에 대한 변환 Rule은 없습니다.

from rest_framework.renderers import JSONRenderer

data = Post.objects.all()
JSONRenderer().render(data)	# Error : 모델 타입 미지원


Serializer 를 통한 JSON 직렬화


Serializer

유효성 검사 및 DB로의 저장을 지원합니다.

  • 데이터 변환 / 직렬화 지원
    querySet / Model객체 ←→ Native Python 데이터타입, JSON/XML 등

  • Django의 Form/ModelForm과 유사
    Serializer는 뷰 응답을 생성하는 데 범용적이고 강력한 방법을 제공.
    ModelSerializer는 Serializer생성을 위한 Shortcut

+ Form 과 Serializer는 유사하다 ?

django의 Form과 DRF의 Serializer는 사용법이 유사합니다. Form 이 HTML Form을 생성하고 요청을 처리하는 것이라면, Serializer 는 다양한 API 요청에 대한 처리(직렬화, 유효성 검사)를 하는 것입니다.

Django Form/ModelFormDRF Serializer/ModelSerializer
폼 필드 지정 혹은 모델로부터 읽어 오기폼 필드 지정 혹은 모델로부터 읽어 오기
Form 태그가 포함된 HTML을 생성FORM 데이터가 포함된 JSON 문자열 생성
입력된 데이터에 대한 유효성 검사 및 흭득입력된 데이터에 대한 유효성 검사 및 흭득

ModelSerializer

🦝 django-rest-framework/rest_framework/serializers.py

QuerySetModel 을 ReturnList 또는 ReturnDict 타입으로 반환해 줍니다. 이러한 ReturnList 와 ReturnDict 또한 JSONRenderer를 통해 json 직렬화가 가능한 객체입니다.

Serializer 를 통한 직렬화는 모델들의 복잡한 릴레이션들이 얽혀있기 때문에 Encoder를 통한 직렬화라기 보다는, DRF 내부 로직을 통한 직렬화 입니다. ModelSerializer 를 통해 Model을 직렬화 가능한 객체로 반환 후, 이 반환된 객체를 직렬화 합니다.

# serializers.py
from rest_framework.serializers import ModelSerializer 

# Post 모델에 대한 ModelSerializer 정의
class PostSerializer(ModelSerializer):
	class Meta:
 		model = Post
  		fields = '__all__'
  • ModelSerializer 를 통한 QuerySet 변환

ReturnList 타입으로 반환 후 json 직렬화를 합니다.

# serializers.py

# Post 모델에 대한 ModelSerializer 정의
...

qs = Post.objects.all()
serializer = PostModelSerializer(qs, many=True) # many=True 유의
serializer.data 	# 1) QuerySet -> ReturnList 로 반환

# 2-1) 파이썬 기본 JSON 변환 활용
import json
json_str_string = json.dumps(serializer.data, ensure_ascii=False)

# 2-2) DRF에서 지원하는 JSON 변환 활용 -> 변환 Rule이 추가된 Encoder
from rest_framework.renderers import JSONRenderer
json_utf8_string = JSONRenderer().render(serializer.data)
  • ModelSerializer 를 통한 Model Instance 변환

ReturnDict 타입으로 반환 후 json 직렬화를 합니다.

# serializers.py

# Post 모델에 대한 ModelSerializer 정의
...

post = Post.objects.first()	
serializer = PostModelSerializer(post) 
serializer.data 		# 1) Model -> ReturnDict 타입 로 반환

# 2-1) 파이썬 기본 JSON 변환 활용
import json
json_str_string = json.dumps(serializer.data, ensure_ascii=False)

# 2-2) DRF에서 지원하는 JSON 변환 활용 -> 변환 Rule이 추가된 Encoder
from rest_framework.renderers import JSONRenderer
json_utf8_string = JSONRenderer().render(serializer.data)

💡 지정이 맞지 않으면 변환 에러 (TypeError, AttirbuteError) 가 발생합니다.

Model 객체에 대해서는 필히 many=False (default) 지정
QuerySet 객체에 대해서는 필히 many=True 지정



Django 뷰에서의 JSON 응답


모든 View 는 HttpResponse 타입의 응답을 합니다. 장고 뷰에서의 응답은 일반적으로 다음 두 가지 방법이 있습니다.

  1. json.dumps 를 통해 직렬화된 문자열을 흭득하여 HttpResponse 를 통해 응답.
  2. json.dumps 가 내장되어 있는 JsonResponse 를 통해 응답. 내부적으로 json.dump 를 사용하며 DjangoJSONEncoder 가 디폴트로 지정되어 있음.

DjangoJSONEncoder 는 QuerySet에 대한 직렬화를 제공하지 않으므로, 커스텀하여 사용합니다. 위에서 정의했던 MyJSONEncoder를 활용해봅니다.

from django.http import JsonResponse

qs = Post.objects.all()	# 직렬화할 QuerySet

encoder = MyJSONEncoder	# DjangoJSONEncoder를 커스텀한 Encoder
safe = False		# True : 변환할 데이터 타입이 dict인지 확인. 
			# QuerySet은 dict 타입이 아니므로 False 입니다.
json_dumps_params = {'ensure_ascii': False}
kwargs = {}

response = JsonResponse(qs, encoder, safe, json_dumps_params, **kwargs)
response
# <JsonResponse status_code=200, "application/json">
response.content.decode('utf8')
# [{'id': 1, 'title': 'first posting', 'message': 'hi'}]



DRF 뷰에서의 JSON 응답


DRF 뷰에서의 응답은 DRF Response를 활용합니다.

from rest_framewor.responsser import Response

qs = Post.objects.all()
serializer = PostModelSerializer(qs, many=True)

response = Response(serializer.data)



실제 DRF Serializer 사용 예시


List 페이지를 만들어 봅니다. 포스팅 조회 응답에 username 도 표현하고싶습니다.
읽기만 하는 필드의 경우 두 가지 방법을 통해 불러올 수 있습니다.

# views.py
from rest_framework import generics

class PostListAPIView(generics.ListAPIView):
	queryset = Post.objects.all()
 	serializer_class = PostModelSerializer
    
post_list = PostListAPIView.as_view()

(1) 명시적으로 필드 지정

FK 키값으로 응답하는 필드를 serializer.ReadonlyField 로 정의합니다.

# serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
	username = serializers.ReadOnlyField(source='author.username')
    
 	class Meta:
  		model = Post
 		fields = ['pk', 'username', 'title', 'content']

(2) 중첩 직렬화 지정

중첩된 Serializer 를 통해 meta 클래스에서 read_only_fieds 로 정의합니다.

# serializers.py
from django.contrib.auth import get_user_model
from rest_framework import serializers
from .models import Post

class AuthorSerializer(serializers.ModelSerializer):
	class Meta:
 		model = get_user_model()
  		read_only_fields = ['username']

class PostSerializer(serializers.ModelSerializer):
	username = serializers.ReadOnlyField(source='author.username')
    
 	class Meta:
  		model = Post
 		fields = '__all__'
 		

📌 참고 출처

post-custom-banner

0개의 댓글