JSON 직렬화


💡 Serialization(직렬화)

모든 프로그래밍 언어의 통신에서 데이터는 필히 문자열 또는 바이트

표현 되어야 한다.

즉, 데이터를 문자열 또는 바이트로 변환해주는 작업을 직렬화라고 한다.

송신자의 경우에는 객체를 문자열로 변환하여, 데이터를 전송하고(직렬화)

수진자의 입장에서는 수신한 문자열을 다시 객체로 변환하여(비직렬화) 데이터를

활용해야 한다. 또한 각 언어에서 모두 지원하는 직렬화 포맷(JSON, XML 등)도

있고, 특정 언어에서만 지원하는 직렬화 포멧(python-pickle)이 있다.



💡 JSON VS Pickle

JSON

1. 다른 언어/플랫폼과 통신할 때 주로 사용.

2. 표준 라이브러리 json 제공

3. Pickle에 비해 직렬화를 지원하는 데이터타입의 수가 적지만, 커스텀 가능

4. 연속성 데이터에 주로 활용

import json

data = [{'message':'django'}]

# 직렬화 - 문자열 타입
json_string = json.dumps(data)
print(json_string) 
# 출력: [{'message':'django'}]

# 비직렬화
print(json.loads(json_string))
# 출력: [{'message':'django'}]


Pickle

1. 파이썬 전용 포맷으로서 파이썬 시스템끼리만 통신할 때 사용 가능

2. 표준 라이브러리 pickle 제공 → json 라이브러리가 유사한 사용 방법

3. 파이썬 버전 특성을 타는 경우가 있음.

4. 임시성 데이터에 주로 활용

import pickle

data = [{'message':'django'}]

# 직렬화 - 바이트 타입
pickle_string = pickle.dumps(data)
print(pickle_string)
"""
b'\x80\x04\x95\x1a\x00\x00\x00\x00\x00\x00\x00]\x94}
\x94\x8c\x07message\x94\x8c\x06django\x94sa.'
"""

# 비직렬화
print(pickle.loads(pickle_string))
# 출력: [{'message': 'django'}]

하지만 JSON/Pickle 모두 파이썬 기본 라이브러리이다.

따라서 장고 타입(Model/Queryset 등)에 대해서는 직력화 Rule이 없다.

만약에 Queryset을 직렬화를 시도할 경우에는 아래와 같은 오류가 발생한다.

TypeError: Object of type QuerySet is not JSON serializable



💡 DjagnoJSONEncoder

위와 같이 에러가 생긴경우 DjagnoJSONEncoder를 시도해서 몇 가지

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)

위와 같이 cls 인자를 사용하여 DjagnoJSONEncoder를 활용할 수 있다.

하지만 여기서도 역시나 TypeError: Object of type QuerySet is not JSON serializable

에러가 발생한다. 그러면 어떻게 위 에러를 해결할 수 있을까??


List-Comprehension을 사용하여 “직접 변환” 해주면 된다.

data = [
    {'id': post.id, 'title': post.title, 'content': post.content} for post in Post.objects.all()
]

json.dumps(data, ensure_ascii=False)

여기서 ensure_ascii 파라미터의 default값은 True이다.

아래와 같이 True일 경우에는 유니코드를 반환해주고,

False일 경우 문자열을 그래도 반환해준다.

import json

print(json.dumps('서울'))
# "\uc11c\uc6b8"

print(json.dumps('서울', ensure_ascii=False))
# "서울"


💡 직접 Rule 지정

하지만 매번 직접 변환하게 되면 시간도 오래걸리고 비효율적이다.

따라서 “직접 변환 Rule”을 지정하는 방법이 최선이다.

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

#커스터 JSON Encoder를 정의
class MyJSONEncoder(DjangoJSONEncoder):
    def default(self,obj):
        if isinstance(obj, Queryset):
            return tuple(obj)

        elif isinstance(obj, Post):
            return {'id': obj.id, 'title': obj.title, 'content': obj.content}

        elif hasattr(obj, 'as_dict'):
            return obj.as_dict()

        # 위의 로직 모두 실패시 
        return super().default(obj)

data = Post.objects.all()

# 직렬화 할 때, 직렬화를 수행해줄 JSON Encoder를 지정해줍니다.
json.dumps(data, cls = MyJSONEncoder, ensure_ascii=False)


💡 DRF - JSONRender

rest_framwork/utils/encoders.py의 JSONEncoder를 통한 직렬화.

Model 타입은 여기서 지원하지 않고, ModelSerializer에서 지원해준다.


  • 장고의 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() 반환



💡 DRF - JSONRenderer

JSONRenderer는 json.dumps를 래핑한 클래스이다.

JSONRenderer는 보다 편리한 직렬화를 지원해주고, UTF-8 인코딩도 추가적으로 수행해준다.

하지만 여기에서도 Model에 대한 변환 Rule은 없다.

from rest_framework.renderers import JSONRenderer

data = Post.objects.all()

serializer = PostSerializer(data, many=True)

JSONRenderer().render(serializer.data)


💡 Model Serializer를 통한 JSON직렬화

Serializer/ModelSerializer 클래스는 타 언어의 서비스 영역을 담당한다고

볼 수 있다. 실제 데이터 생성, 수정, 조회 로직을 구현할 수 있다.

또한 Form/ModelForm과 유사한 측면이 있지만 기능이 조금 더 많다.

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

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

# forms.py
from django.forms import ModelForm
from .models import Post

class PostForm(ModelFrom):
    class Meta:
        model = Post
        fields = '__all__'


ModelForm과 유사한 ModelSerializer

데이터 한개만 넘길 경우는 아래와 같이.

from rest_framework.serializers import ModelSerializer
from .models import Post

class PostSerializer(ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

# Post 타입
post = Post.objects.first()

serializer = PostSerializer(post)

# ReturnDict 타입
serializer.data


데이터 두개 이상 넘길 경우에는 many=True를 꼭 지정해줘야한다.

이 경우에 반환 값은 list와 OrdererDict의 조합이다.



ReturnDict 타입

실제 serializer.data의 데이터 타입이다.

OrderedDict를 상속 받았으며, serializer를 키워드 인자로 받는다.

class ReturnDict(OrderedDict): 
    def __init__(self, *args, **kwargs):
        self.serializer = kwargs.pop('serializer')
        super().__init__(*arg, **kwargs)



ModelSerializer의 QuerySet 변환 지원

Model의 객체에 대해서는 many=False(default)를 지정해줘야 한다.

반면에, QuerySet 객체에 대해서는 many=True를 지정해줘야 한다.

만약 many의 지정이 옳지 않을 경우 TypeError, AttributeError가 발생한다.

# 파이썬 기본 JSON 변환 사용 활용 
# 문자열 형식
import json
json_str_string = json.dumps(serializer.data, ensure_ascii=False) 

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