JSON 직렬화
모든 프로그래밍 언어의 통신에서 데이터는 필히 문자열로 표현되어야만 합니다.
(즉 현재 프로그램 밖으로 뭔가 보내야할때 문자열로 해야하므로 직렬화 필요
반대로 프로그램 밖에서 데이터가져오면 비직렬화 과정 꼭 필요)
- 송신자 : 객체를 문자열로 변환하여, 데이터 전송 => 이를 직렬화 (Serialization) 라고 합니다. (api의 클라이언트)
- 수신자 : 수신한 문자열을 다시 객체로 변환하여, 활용 => 이를 비직렬화 혹은 역직렬화 (Deserialization) 라고 합니다. (api 서버)
각 언어에서 모두 지원하는 직렬화 포맷 (JSON, XML 등) 도 있고, 특정 언어에서만 지원하는 직렬화 포맷 (파이썬은 Pickle) 이 있습니다.
JSON
보통의 웹애플리케이션에서는 일반적으로 웹브라우저가 주 클라이언트 프로그램이기 때문에, 주로 HTML포맷으로 통신을 합니다. 그런데, 최근의 API 서버에는 대개 JSON 포맷으로 통신을 수행합니다. 그렇기에 항상 장고 API 뷰 함수에서는 최종적으로 JSON포맷의 문자열 응답을 해야 합니다.
- JSON 포맷 : 다른 언어/플랫폼과 통신할 때 주로 사용합니다. 표준 라이브러리json이 제공됩니다.
- pickle에 비해 직렬화를 지원하는 데이터타입의 수가 적습니다. 공통 데이터타입에 한해서만 직렬화를 지원합니다.
- PICKLE 포맷 : 파이썬 전용 포맷으로서 파이썬 시스템끼리 통신할 때 사용합니다만, 최근 파이썬끼리 통신에 json포맷도 많이 사용합니다. 표준 라이브러리pickle이 제공됩니다.
- JSON에서 지원하지않는 파이썬 데이터타입을 지원합니다.
- 파이썬 버전 특성을 탑니다.
다음과 같은 파이썬 객체가 있습니다.
In [1]: #객체임!!
1 | post_list = [ |
다음과 같이 JSON 포맷의 문자열로 직렬화를 할 수 있습니다.
In [2]: #s는 spring 의미
1 | import json |
Out[2]: #문자열임!! 따라서 index[0]=’ 이고 [1]=[
1 | '[{"message": "hello askdjango"}]' |
거꾸로 비직렬화도 가능합니다.
In [3]: #s는 spring 의미
1 | json.loads(json_string) |
Out[3]:
1 | [{'message': 'hello askdjango'}] |
JSON 직렬화와 유사한 방식으로, PICKLE 포맷의 문자열로 직렬화를 할 수 있습니다.
In [4]:
1 | import pickle |
Out[4]:
1 | b'\x80\x03]q\x00}q\x01X\x07\x00\x00\x00messageq\x02X\x0f\x00\x00\x00hello askdjangoq\x03sa.' |
이 역시 비직렬화가 가능합니다
In [5]:
1 | pickle.loads(pickle_data) |
Out[5]:
1 | [{'message': 'hello askdjango'}] |
json/pickle 라이브러리는 파이썬 표준 라이브러리로서 파이썬 표준 데이터타입에 대한 직렬화/비직렬화를 수행해줍니다. 이는 파이썬 표준 데이터타입에 대해서는 각각의 타입에 대해서 직렬화/비직렬화 룰을 파이썬이 지원해주고 있기 때문입니다. 하지만 장고 Model/QuerySet과 같은 파이썬 언어 외부타입에 대해서는 파이썬의 json 모듈은 직렬화/비직렬화 Rule을 모르기에 직렬화가 불가합니다.
이미 만들어둔 장고 프로젝트가 있으시다면, 장고 쉘을 통해 확인해보실 수 있습니다. User 모델 인스턴스에 대해 JSON직렬화를 수행했는데, TypeError가 발생했으며 “Objects of type ‘User’ is not JSON serializable.” 메세지가 나옵니다.
1 | 쉘> python3 manage.py shell |
Error: django에 데이터타입인 User에 대해 직렬화 비직렬화로직은 파이썬 표준json라이브러리가 알수없기때문에 하지못하는것(알려주면 가능해짐)
이제 장고의 데이터타입에 대해 JSON 직렬화를 수행하는 방법에 대해서 살펴보겠습니다.
Django 프로젝트 기본 셋업
본 에피소드를 시작하기에 앞서, Jupyter Notebook을 통해 직렬화 연습을 해보기 위해, Jupyter Notebook에서 Django 프로젝트 세팅해서 모델 돌려보기 내역을 먼저 수행해주세요. 해당 내역을 잘 수행하셨다면, 다음 코드처럼 Post
모델을 통해 DB 쿼리하실 수 있어요.
jupyter notebook에서 장고 3.0이상에서 DB접근시 필요한것
1 os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"]="true"
모델 돌려보기 해당 내용
django로 간단한 장고 코드 검증을 위해 jupyter notebook밖에서 장고 프로젝트를 생성/세팅하는것은 번거로운일. Jupyter notebook밖으로 나가고싶지않다. >> 가능함
최소한의 settings
model을 쓸려면 데이터베이스가 필요하다, 이 역시 별도 DB세팅을 하는 것은 사치이므로, SQLite3를 메모리 베이스로 세팅함.
Tip: 실제 장고 프로젝트에서 프로젝트와 연동되는 jupyter notebook을 쓰고자 한다면 django-extensions의 shell_plus 명령을 써서 가능
Jupiter notebook python파일 만들고 시작
1 | import django |
1 | form = QuizForm(data) |
참고로 request.POST도 querydic형태로 사전형태와 유사한 데이터를 제공
모델 정의(지금 파일이아닌 메모리에 DB가 있을뿐, migration은 해줘야함)
Tip: python manage.py sqlmigrate <앱이름> <migrate번호>로 migration spq내역확인가능
아래는 jupyter notebook 에서 Post모델을 통해 DB퀴리가 가능해짐
1 | from django.db import models |
Out[1]:
1 | <Post: 저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는> |
자. 이제 DB에서 쿼리도 잘 됩니다. :D
In [2]:
1 | Post.objects.all() |
Out[2]:
1 | <QuerySet [<Post: 횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭>, <Post: '디지털세대, 아날로그에 빠지다'…아날로그 인기 이유는?>, <Post: 저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는>]> |
In [5]:
1 | for post in Post.objects.filter(): |
장고의 JSON 직렬화
현재는 그냥 import json하면 파이썬 표준 라이브러리 사용됨
장고에서는 파이썬 표준 라이브러리 json 모듈을 그대로 쓰지 않고, django/core/serizliers/json.py
의 DjangoJSONEncoder 클래스
를 통한 직렬화를 수행합니다.
DjangoJSONEncoder
는 json.JSONEncoder
를 상속받았으며, 다음 타입에 대한 직렬화를 추가로 지원합니다.(주로 date 쪽)
datetime.datetime
datetime.date
datetime.time
datetime.timedelta
decimal.Decimal
,uuid.UUID
그런데, 이는 파이썬 기본 데이터 타입에 대한 직렬화가 추가되었을 뿐, 장고 데이터타입인 QuerySet과 Model 인스턴스에 대한 직렬화는 지원하지 않습니다. 장고는 웹 애플리케이션을 만들기 위한 웹프레임워크이고 웹 애플리케이션 개발에서는 JSON직렬화할 일이 적긴 합니다. 그렇지만 기본에서 제공해주면 좋았을 텐데요 … 아쉽습니다. 이 가려운 부분을 djangorestframework
가 긁어줍니다. :D 이는 뒤에서 살펴보구요.
우선 장고 기본에서 제공해주는 DjangoJSONEncoder
를 실행해봅시다.
In [9]:
1 | import json |
In [6]:
1 | data = Post.objects.all() |
Out[6]:
1 | <QuerySet [<Post: 횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭>, <Post: '디지털세대, 아날로그에 빠지다'…아날로그 인기 이유는?>, <Post: 저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는>]> |
이렇게 직렬화할 데이터를 QuerySet으로 준비합니다. 그리고 직렬화를 수행해봅니다.
json.dumps(data)를 하면 파이썬 표준 json이 써지므로 QuerySet에 대해서 직렬화 로직없음 따라서
TypeError: Object of type 'QuerySet' is not JSON serializable
예외가 발생할 거예요. :(
In [11]: # cls라는 이름으로 해당 클래스를 넘겨준다면 커스텀 인코더 사용가능
1 | json.dumps(data, cls=DjangoJSONEncoder) |
또 오류가 발생. 왜죠? 왜일까요? DjangoJSONEncoder는 QuerySet의 직렬화/비직렬화방법을 모르고 있기 때문에, not JSON serializable 오류가 발생한 겁니다. 그렇다면, 어떻게 해야할까요?
QuerySet
을 파이썬 표준 데이터타입의 값으로 한땀 한땀 직접 변환을 할 수 있겠습니다. 이는 json모듈이 하던 일을 직접 하는 것이죠.
In [14]:
1 | data = [ |
Out[14]:
1 | '[{"id": 1, "title": "횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭", "content": "교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다."}, {"id": 2, "title": "\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?", "content": "옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다."}, {"id": 3, "title": "저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는", "content": "늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?"}]' |
In [6]:
1 | import json |
Out[6]:
1 | '["\\uc548\\ub155", "\\ud30c\\uc774\\uc36c"]' |
In [7]:
1 | json.dumps(mydata, ensure_ascii=False) |
Out[7]:
1 | '["안녕", "파이썬"]' |
“소곤소곤. json에게 직렬화 방법을 알려줄 수도 있어요. 어떻게 하느냐??? DjangoJSONEncoder
가 직렬화 방법을 알고 있기에, 이를 확장하면 됩니다. 다음 2가지 타입을 지원할 수 있도록 해보겠습니다.
- QuerySet 타입 : tuple 타입으로 변환
- Post 타입 : dict 타입으로 변환
In [15]: #커스텀으로
1 | from django.core.serializers.json import DjangoJSONEncoder |
Out[15]:
1 | '[{"id": 1, "title": "횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭", "content": "교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다."}, {"id": 2, "title": "\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?", "content": "옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다."}, {"id": 3, "title": "저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는", "content": "늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?"}]' |
django-rest-framework
에서도 커스텀 JSON Encoder
를 만드는 방식으로, JSON 인코딩을 처리하고 있습니다.
rest_framework.renderer.JSONRender 의 직렬화 방식
rest_framework/utils/encoders.py
의 JSONEncoder 클래스
를 통한 직렬화를 수행합니다.
JSONEncoder
는 장고의 DjangoJSONEncoder
를 상속받지는 않고, json.JSONEncoder
를 직접 상속받아 다음 타입에 대한 직렬화를 추가로 지원합니다.( 까보면알수있다.)
- 파이썬 표준 데이터 타입
- datetime.datetime 타입
- datetime.date 타입
- datetime.time 타입
- datetime.timedelta 타입
- decimal.Decimal 타입
- uuid.UUID 타입
- six.binary_type 타입
__getitem__
함수를 지원할 경우,dict(obj)
의 리턴값을 취함__iter__
함수를 지원할 경우,tuple(item for item in obj)
의 리턴값을 취함
- 장고 데이터 타입
- QuerySet 타입일 경우,
tuple(obj)
의 리턴값을 취함. - .tolist 함수를 가질 경우,
obj.tolist()
의 리턴값을 취함.
- QuerySet 타입일 경우,
QuerySet
에 대한 직렬화를 지원해줍니다만,Model
타입에 대한 직렬화는 없습니다. 이는 ModelSerializer
의 도움을 받습니다. (까보면 없다)
Tip: QuerySet타입은 SQL로 해석 CRUD 구현된다.
rest_framework/renderer.py
내 JSONRenderer
는 json.dumps
함수에 대한 래핑 클래스입니다. 보다 편리한 JSON 직렬화를 도와줍니다. 다음 코드로 직렬화를 수행하실 수 있어요. utf8 인코딩도 추가로 수행해줍니다. (즉 래핑하고있어서 클래스내에 render()함수호출하면 json.dumps일어나게되어있다)
In [21]:
1 | from rest_framework.renderers import JSONRenderer |
Out[21]:
1 | '{"이름":"AskDjango"}' |
위에서 살펴보셨다시피, JSONRenderer
은 rest_framework.utils.encoders.JSONEncoder
를 사용합니다. JSONEncoder
는 Model
타입에 대한 직렬화를 지원하지 않기에 직렬화에 실패합니다.
In [18]:
1 | from rest_framework.renderers import JSONRenderer |
In [19]:
1 | JSONRenderer().render(data) |
이 역시, 직접 직렬화를 한땀한땀할 수도 있겠지만 …
In [20]:
1 | data = [ |
Out[20]:
1 | '[{"id":1,"title":"횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭","content":"교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다."},{"id":2,"title":"\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?","content":"옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다."},{"id":3,"title":"저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는","content":"늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?"}]' |
이 역시, django-rest-framework
에서 사용하는 JSONEncoder
를 확장해 볼수도 있겠지만 …
In [22]: #확장.
1 | from rest_framework.renderers import JSONRenderer |
Out[22]:
1 | '[{"id":1,"title":"횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭","content":"교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다."},{"id":2,"title":"\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?","content":"옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다."},{"id":3,"title":"저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는","content":"늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?"}]' |
ModelSerializer를 통한 JSON 직렬화(중요)
django-rest-framework
에서는 일반적으로 ModelSerializer
를 통해 JSONRenderer
에서 변환가능한 형태로 먼저 데이터를 변환합니다.
Serializer
는 장고의 Form
과 유사하며, ModelSerializer
는 장고의 ModelForm
과 유사합니다. 역할 면에서 Serializer
는 POST
요청만 처리하는 Form
이라 할 수 있습니다.
Django Form/ModelForm | Django Serializer/ModelSerializer |
---|---|
폼 필드 지정 혹은 모델로부터 읽어오기 | 동일 |
Form HTML을 생성 | JSON 문자열을 생성 |
입력된 데이터에 대한 유효성 검사 및 획득 | 동일 |
다음과 같이 ModelSerializer
를 정의합니다. ModelForm
과 거의 판박이입니다. :D
In [24]:
1 | from rest_framework.serializers import ModelSerializer |
다음과 같이 Post 모델 인스턴스에 대해서도 dict타입으로 변환을 지원합니다. PostModelSerializer
에 Post
객체를 넘겨보세요.
In [31]:
1 | post = Post.objects.first() # Post 타입 |
Out[31]:
1 | <Post: 횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭> |
In [32]: 사전을 바로 만들어줌
1 | serializer = PostModelSerializer(post) |
Out[32]:
1 | ReturnDict([('id', 1), |
Tip: 위 serializer.data
는 ReturnDict
타입입니다. OrderedDict
을 상속받았으며, 생성자를 통해 serializer
필드를 추가로 받습니다. (약간 몇가지 필드가 추가된 사전)
1 | class ReturnDict(OrderedDict): |
Tip:
- 기존 dic은 {‘a’:1, ‘b’:2,} 여기에다가 추가하면 순서가 뒤죽박죽 바뀔수있다(내가넣은순 유지못함)
- orderedDic은 넣은 순서를 가지게됨
1 | d2 = OrderedDict() |
(???) QuerySet타입이고 Post는 타입??
QuerySet 변환 지원
ModelSerializer
는 QuerySet
에 대해서도 변환을 지원해줍니다. ModelSerializer
의 many
인자는 디폴트 False
입니다. many=True
인자를 지정해줘야만 QuerySet
을 처리합니다.
In [33]:
1 | serializer = PostModelSerializer(Post.objects.all(), many=True) # QuerySet을 지정할 경우, 필히 many=True 지정 , 모델인스턴스만 넘길때는 필요없음 |
Out[33]:
1 | [OrderedDict([('id', 1), ('title', '횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭'), ('content', '교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다.'), ('created_at', '2017-10-16T14:15:12.102064'), ('updated_at', '2017-10-16T14:15:12.102093')]), OrderedDict([('id', 2), ('title', "'디지털세대, 아날로그에 빠지다'…아날로그 인기 이유는?"), ('content', '옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다.'), ('created_at', '2017-10-16T14:15:12.102431'), ('updated_at', '2017-10-16T14:15:12.102446')]), OrderedDict([('id', 3), ('title', '저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는'), ('content', '늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?'), ('created_at', '2017-10-16T14:15:12.102714'), ('updated_at', '2017-10-16T14:15:12.102729')])] |
In [35]:
1 | import json |
Out[35]:
1 | '[{"id": 1, "title": "횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭", "content": "교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다.", "created_at": "2017-10-16T14:15:12.102064", "updated_at": "2017-10-16T14:15:12.102093"}, {"id": 2, "title": "\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?", "content": "옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다.", "created_at": "2017-10-16T14:15:12.102431", "updated_at": "2017-10-16T14:15:12.102446"}, {"id": 3, "title": "저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는", "content": "늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?", "created_at": "2017-10-16T14:15:12.102714", "updated_at": "2017-10-16T14:15:12.102729"}]' |
In [36]:
1 | from rest_framework.renderers import JSONRenderer |
Out[36]:
1 | '[{"id":1,"title":"횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭","content":"교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다.","created_at":"2017-10-16T14:15:12.102064","updated_at":"2017-10-16T14:15:12.102093"},{"id":2,"title":"\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?","content":"옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다.","created_at":"2017-10-16T14:15:12.102431","updated_at":"2017-10-16T14:15:12.102446"},{"id":3,"title":"저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는","content":"늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?","created_at":"2017-10-16T14:15:12.102714","updated_at":"2017-10-16T14:15:12.102729"}]' |
뷰에서의 Json 응답
장고 스타일
JSON 포맷으로 직렬화된 문자열은 장고 뷰를 통해서 응답이 이뤄져야 합니다. 다음 2가지가 가능합니다.
json.dumps
를 통해 직렬화된 문자열을HttpResponse
를 통해 응답json.dumps
기능을 제공하는JsonResponse
를 즉시 사용
이 중에 2번째 방법을 사용해보겠습니다. 이때 JsonResponse
는 장고의 DjangoJSONEncoder
를 사용하고 있으니, QuerySet
에 대해서는 직렬화가 불가능합니다. 그래서 위에서 정의한 MyJSONEncoder
를 활용해보겠습니다.
In [38]:
1 | # 직렬화할 QuerySet 준비 |
Out[38]:
1 | <QuerySet [<Post: 횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭>, <Post: '디지털세대, 아날로그에 빠지다'…아날로그 인기 이유는?>, <Post: 저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는>]> |
JsonResponse
에 넘겨줄 인자를 준비합니다.
encoder
(디폴트:DjangoJSONEncoder
) : JSON 인코딩을 수행할 클래스safe
(디폴트: True) : 변환할 데이터의dict타입
체킹을 목적으로 합니다. 데이터가dict
타입이 아닐 경우에는 필히False
를 지정해주세요. 미지정 시에TypeError
예외를 발생시킵니다.json_dumps_params
(디폴트:None
) :json.dumps
에 넘겨질 인자kwargs
(디폴트: {}) : 부모 클래스인HttpResponse
에 넘겨질 인자
In [40]:
1 | encoder = MyJSONEncoder |
다음과 같이 Http 응답을 생성하고, 그 응답바디를 출력해봅시다.
In [42]:
1 | from django.http import JsonResponse |
Out[42]:
1 | '[{"id": 1, "title": "횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭", "content": "교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다."}, {"id": 2, "title": "\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?", "content": "옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다."}, {"id": 3, "title": "저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는", "content": "늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?"}]' |
django-rest-framework 스타일 (맛보기)
django-rest-framework
에서의 JSON 직렬화 코드를 러프하게 살펴보겠습니다. 이어지는 코드가 많아보이지만, 실제로 사용될 때에는 코드가 아주 심플합니다. 디폴트 세팅된 항목들이 사용되기 때문입니다.
In [59]:
1 | # 변환할 데이터로서 QuerySet을 준비 |
In [60]:
1 | # queryset을 통해 ModelSerializer 준비 |
Out[60]:
1 | PostModelSerializer(<QuerySet [<Post: 횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭>, <Post: '디지털세대, 아날로그에 빠지다'…아날로그 인기 이유는?>, <Post: 저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는>]>, many=True): |
In [61]:
1 | # 짠~ 이렇게, 직렬화할 데이터가 뽑아졌습니다. |
Out[61]:
1 | [OrderedDict([('id', 1), ('title', '횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭'), ('content', '교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다.'), ('created_at', '2017-10-16T14:15:12.102064'), ('updated_at', '2017-10-16T14:15:12.102093')]), OrderedDict([('id', 2), ('title', "'디지털세대, 아날로그에 빠지다'…아날로그 인기 이유는?"), ('content', '옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다.'), ('created_at', '2017-10-16T14:15:12.102431'), ('updated_at', '2017-10-16T14:15:12.102446')]), OrderedDict([('id', 3), ('title', '저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는'), ('content', '늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?'), ('created_at', '2017-10-16T14:15:12.102714'), ('updated_at', '2017-10-16T14:15:12.102729')])] |
뷰에서는 Response
를 통해 응답을 생성합니다. 이는 HttpResponse
를 상속받은 클래스입니다. Response
는 단순히 JSON 직렬화
뿐만 아니라, HTTP
요청에 따라 다양한 포맷으로 변환(Render
)하여 응답을 생성할 수 있습니다.
In [63]:
1 | from rest_framework.response import Response |
Out[63]:
1 | <Response status_code=200, "text/html; charset=utf-8"> |
Response
객체에 변환에 필요한 속성을 지정해줘야합니다. 실제 요청을 처리하는 코드에서는 APIView
클래스에 의해서 디폴트 지정이 되므로, 대개 수동으로 지정할 일은 없습니다.
아래 코드들은 위에서의 class PostViewSet(viewers.ModelViewSet): 에서 ModelViewSet에서 이미 APIView를 상속중
In [64]:
1 | from rest_framework.views import APIView |
In [67]:
1 | response |
Out[67]:
1 | <Response status_code=200, "application/json"> |
response
객체는 아직 변환할 준비만 하고 있을 뿐, 아직 JSON 직렬화
변환은 수행하지 않았습니다. .rendered_content
속성에 접근할 때, 변환이 이뤄집니다.
In [68]: #이 시점에 직렬화 수행함
1 | response.rendered_content.decode('utf8') |
Out[68]:
1 | '[{"id":1,"title":"횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭","content":"교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다.","created_at":"2017-10-16T14:15:12.102064","updated_at":"2017-10-16T14:15:12.102093"},{"id":2,"title":"\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?","content":"옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다.","created_at":"2017-10-16T14:15:12.102431","updated_at":"2017-10-16T14:15:12.102446"},{"id":3,"title":"저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는","content":"늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?","created_at":"2017-10-16T14:15:12.102714","updated_at":"2017-10-16T14:15:12.102729"}]' |
실전에서의 Response 활용
위에서는 디테일하게 django-rest-framework
뷰에서의 JSON 직렬화
순서에 대해서 살펴봤는데요. 실제로는 다음과 같이 간결하게 사용합니다.
In [84]: # ListAPIView도 뜯어보면 ModelViewSet중에 몇개만 구성되어있음. 비슷하게 모두구성됨
1 | from rest_framework import generics |
이렇게 간결하게 정의한 뷰 만으로 다음과 같이 JSON 응답을 만들어낼 수 있습니다.(router불필요)
Tip: ModelViewSet은 urls.py에서도 여러가지 url들을 활용해야하므로 urlpatterns= [ ]앞에 아래코드가 꼭 필요하다 (ViewSet은 뷰자체가 4개임)
1 | router = DefaultRouter() |
In [86]: (???)
1 | from django.http import HttpRequest |
Out[86]:
1 | '[{"id":1,"title":"횡단보도 보행자 없으면 우회전 가능?…혼란 빚은 까닭","content":"교차로에서 우회전할 때 횡단 보도를 건너는 사람이 없다면 보행자 신호가 녹색이더라도 진입할 수 있을까요? 이 문제를 놓고 대법원과 경찰의 판단이 다른 상황입니다.","created_at":"2017-10-16T14:15:12.102064","updated_at":"2017-10-16T14:15:12.102093"},{"id":2,"title":"\'디지털세대, 아날로그에 빠지다\'…아날로그 인기 이유는?","content":"옛 방식을 고집하는 아날로그 공간들이 젊은 층을 중심으로 주목받고 있습니다.","created_at":"2017-10-16T14:15:12.102431","updated_at":"2017-10-16T14:15:12.102446"},{"id":3,"title":"저녁 줄였는데 누구는 살 빠지고, 난 안 빠지고…이유는","content":"늦은 시간에 야식 먹으면 다 살로 간다고 하죠? 그래서 야식 증후군이란 말까지 생겼습니다. 또 아침은 많이 먹고 저녁은 되도록 적게 먹는 것이 다이어트의 지름길이라고 생각하기도 합니다. 이게 다 얼마나 맞는 말일까요?","created_at":"2017-10-16T14:15:12.102714","updated_at":"2017-10-16T14:15:12.102729"}]' |