DRF 공부하기 (1) :: JSON

PHYYOU·2020년 10월 13일
1

출처


위의 블로그의 내용을 복붙함.

django rest framework 를 위한 JSON

JSON

JSON 은 JavaScript Object Notation 의 줄임말로서 데이터의 송수신을 자바스크립트 객체로서 수행할 수 있게끔 하는 가벼운 문자열 데이터 표현식입니다.

JSON 이 등장하기 이전에는 XML 이 그 역할를 독차지 하고 있었습니다. 하지만 XML 은 비교적 크기가 무겁다는 이유로 요즘은 JSON 이 더 많은 지분을 확보하고 있습니다.

XML 이란?

eXtensible Markup Language 의 줄임말로서 html 과 많이 비교됩니다. 둘의 결정적인 차이는 html 은 테그들을 통해서 데이터를 표현하는 마크업 언어이고 XML 은 데이터를 설명하는 마크업 언어입니다. json 과 마찬가지로 웹상에서 주고 받는 데이터입니다.

JSON 은 다음과 같이 사용합니다.

{
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": true,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    },
    {
      "name": "Madame Uppercut",
      "age": 39,
      "secretIdentity": "Jane Wilson",
      "powers": [
        "Million tonne punch",
        "Damage resistance",
        "Superhuman reflexes"
      ]
    }
  ]
}

django 에서의 JSON

그렇다면 JSON 이 django 에서 어떻게 쓰일까요?

django rest framework 전까지는 클라이언트의 요청에 대해 서버는 우리가 잘 알고 있는 html, css, javascript 등을 보내주었습니다. django rest framework 는 클라이언트의 요청에 대해 JSON 을 돌려줌으로서 소통을 합니다.

위에 예시를 보면 알겠지만 JSON 은 겉보기에 자바스크립트 객체와 같은 형태입니다. 그렇다면 JSON과 자바스크립트 객체는 같은 것일까요? 아닙니다. 만약 자바스크립트 객체를 통해 데이터를 전송하다보면 자바스크립트 객체라는 타입을 인지 못하는 경우가 발생할 수 있습니다. 따라서 가장 일반적인 자료형인 문자열로 데이터를 주고 받아 이러한 문제를 해결합니다. 즉, JSON 도 문자열 타입입니다.

자바스크립트 객체를 JSON 으로 바꾸는 것을 직렬화, Serialization 이라고 합니다.

python에서 json 다루기

이제 python 에서 json 을 다뤄봅시다. 다행히도 python 은 json 을 import 만 해주면 쉽게 사용할 수 있습니다.

myDataJSON 과 같은 형태로 선언을 해줍시다. 사실 python 에서도 다음과 같이 선언하는 자료형이 존재합니다. 바로 딕셔너리 입니다. 따라서 myData 의 타입은 현재 dict 입니다.

import json

myData = {
  "squadName": "Super hero squad",
  "homeTown": "Metro City",
  "formed": 2016,
  "secretBase": "Super tower",
  "active": True,
  "members": [
    {
      "name": "Molecule Man",
      "age": 29,
      "secretIdentity": "Dan Jukes",
      "powers": [
        "Radiation resistance",
        "Turning tiny",
        "Radiation blast"
      ]
    }
  ]
}

print(type(myData)) # <class 'dict'>

이를 JSON 으로 바꾸는 건 아주 간단합니다. json 내에 dumps 를 통해 dict 를 JSON 의 타입인 str 로 변환이 가능합니다.

myDataJson = json.dumps(myData)

print(type(myDataJson)) # <class 'str'>

반대로 JSON 을 원래의 python dict 로 원상복귀하는 과정도 아래와 같이 loads 를 통해 간단히 할 수 있습니다.

myDataReturn = json.loads(myDataJson)

print(type(myDataReturn)) # <class 'dict'>

django rest framework 를 위한 JSON 직렬화

ModelSerializer 를 통한 JSON 직렬화

DRF 에서는 ModelSerializer 를 통해 JSONRenderer 에서 변환가능한 형태로 먼저 데이터를 변환합니다. Serializer 는 장고의 Form 과 유사하며 ModelSerializer는 장고의 ModelForm과 유사합니다.

둘의 결정적인 차이는 Form 은 html 을 생성하고 Serializer는 JSON 문자열을 생성하는 차이가 있습니다.

실습을 위하여 간단히 모델을 정의하였습니다.

# models.py

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=100)
    message = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

Form 을 사용하는 것과 유사하게 Serializer 를 만들어줍니다.

# serializers.py

from rest_framework import serializers
from .models import Post

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

urls 에 대해선 뒤에서 자세히 다뤄보도록 하겠습니다.

# config/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('core/', include('core.urls'), name='core'),
]

# core/urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register(r'posts', views.PostViewSet)

urlpatterns = [
    path('', include(router.urls)),
]
# views.py

from django.shortcuts import render
from rest_framework import viewsets
from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

이에 대해서 실습을 진행하였습니다.

serializer = PostSerializer(post)
serializer.data
# {'id': 2, 'title': '제목 내용', 'message': '메세지 내용', 'created_at': '2019-11-18T18:52:57.489893Z', 'update_at': '2019-11-18T19:22:53.717588Z'}
type(serializer.data) 
# <class 'rest_framework.utils.serializer_helpers.ReturnDict'> 

PostSerializer 를 통해 Post 객체를 dict 타입으로 변환할 수 있습니다. 특이한 점은 직렬화된 데이터의 타입이 ReturnDict 입니다. 이는 순서있는 사전형을 의미하는 OrderedDict 를 상속받았습니다.

serializer = PostSerializer(Post.objects.all())
serializer.data
# 오류

serializer = PostSerializer(Post.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 2), ('title', '제목 내용'), ('message', '메세지 내용'), ('created_at', '2019-11-18T18:52:57.489893Z'), ('update_at', '2019-11-18T19:22:53.717588Z')]), OrderedDict([('id', 3), ('title', '임시제목'), ('message', 'ㅡㅏㅏ'), ('created_at', '2019-11-18T18:52:57.489893Z'), ('update_at', '2019-11-18T18:52:57.499208Z')])]
type(serializer.data) 
<class 'rest_framework.utils.serializer_helpers.ReturnList'>

ModelSerializer 를 상속받은 PostSerializer 는 QuerySet 에 대해서도 변환을 지원해줍니다. 단 이 때는 직렬화 할 시에 many 의 인자를 True 로 넘겨줘야 합니다.






뷰에서의 JSON 응답

Django 스타일

JSON 포맷으로 직렬화된 문자열은 views 에서 클라이언트로 넘겨줘야 합니다. 넘기는 방식은 두 가지가 있습니다.

  • json.dumps 를 통해 직렬화한 문자열을 HttpResponse 를 통해 응답
  • json.dumps 가 내장되어 있는 JsonResponse 를 통해 응답

이 때 JsonResponse 는 장고의 DjangoJSONEncoder 를 사용하고 있으며, 이는 QuerySet 에 대한 직렬화를 제공하지 않습니다. 따라서 이를 커스튬하여 사용하도록 하겠습니다.

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

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, 'message': obj.message }
        return super().default(obj)

QuerySet 타입에 대해서는 tuple로 변환하고, Post 타입에 대해서는 dict로 변환해줍니다. 그 외에는 DjangoJSONEncoder 를 따릅니다.

이제 Http 응답을 생성하고 이를 확인해보았습니다.

from django.http import JsonResponse

data = Post.objects.all() # 직렬화할 QuerySet
encoder = MyJSONEncoder # DjangoJSONEncoder를 커스튬한 Encoder
safe = False # default = True 로서 변환할 데이터의 타입이 dict인지 확인합니다. dict 가 아닐 경우에는 False로 설정해주어야 합니다. QuerySet 은 dict 타입이 아니므로 False로 설정합니다.
json_dumps_params = {'ensure_ascii':False} # 한글 등의 유니코드는 16진수로 표현되므로 이를 False 로 바꿔주면 한글문자가 그대로 출력됩니다.
kwargs = {}

response = JsonResponse(data, encoder, safe, json_dumps_params, **kwargs)

response
# <JsonResponse status_code=200, "application/json">
response.content.decode('utf8')
# [{"id": 2, "title": "제목 내용", "message": "메세지 내용"}, {"id": 3, "title": "임시제목", "message": "ㅡㅏㅏ"}]

상태코드 200으로 성공했음을 확인할 수 있고 데이터도 정상적인 것을 확인할 수 있습니다.


DRF 스타일

queryset = Post.objects.all()
serializer = PostModelSerializer(queryset, many=True)
serializer.data
# [OrderedDict([('id', 2), ('title', '제목 내용'), ('message', '메세지 내용'), ('created_at', '2019-11-18T18:52:57.489893Z'), ('update_at', '2019-11-18T19:22:53.717588Z')]), OrderedDict([('id', 3), ('title', '임시제목'), ('message', 'ㅡㅏㅏ'), ('created_at', '2019-11-18T18:52:57.489893Z'), ('update_at', '2019-11-18T18:52:57.499208Z')])]

from rest_framework.response import Response

queryset = Post.objects.all()
serializer = PostSerializer(queryset, many=True)

response = Response(serializer.data)
print(response)
# <Response status_code=200, "text/html; charset=utf-8">

profile
박효영

0개의 댓글