[DRF] 직렬화 vs 역직렬화

Jinhyung Rhee·2022년 8월 13일
1

목표

  • TODO: 이처럼 pk값이 아니라 각 제목이 Response되도록 Serializer를 설정하기!

Serializer 출력 포맷

  • 간단한 Serializer의 경우
    class CommentSerializer(serializers.ModelSerializer):
        class Meta:
            model = Comment
            fields = '__all__'
            # fields = ['id', 'title', 'image', 'like', 'category']  
    • class Meta:modelfields를 정해주는 방식으로 진행
    • 프로젝트가 커지면서 Serializer의 코드가 점점 복잡해질수록, 이는 적합한 방식이 아니게됨!
    • django shell에서 확인
      >>> from api2.serializers import *
      >>> 
      >>> CommentSerializer()
      CommentSerializer():
          id = IntegerField(label='ID', read_only=True)
          content = CharField(label='CONTENT', style={'base_template': 'textarea.html'})
          create_dt = DateTimeField(label='CREATE DT', read_only=True)
          update_dt = DateTimeField(label='UPDATE DT', read_only=True)
          post = PrimaryKeyRelatedField(allow_null=True, queryset=Post.objects.all(), required=False)
      • 코드에서는 modelfields만 정의해줬지만, DRF 내부에서는 필드명각 필드별 속성을 자동으로 지정해서 사용함!
      • 필요에 따라 직접 필드명필드별 속성을 지정해서 사용할 줄 알아야 함!

직렬화/역직렬화

  • Serializer 클래스는 Form vs Model 어느쪽에 더 가까운가?
    • Form 클래스 : HTML <form>을 다루기 위해 만들어진 클래스
    • Model 클래스 : DB Table을 다루기 위해 만들어진 클래스
      • 겉으로 보기에 Form 클래스와 Model 클래스는 다른 목적인 것처럼 보이지만, 내부적으로는 거의 비슷한 기능을 제공하는 클래스들임!
      • 이 두 클래스 모두 ①직렬화 기능(Serialization)②유효성 검사 기능(Validation)을 제공하고 있음!
    • ①직렬화 기능(Serialization)②유효성 검사 기능(Validation)을 제공하는 클래스가 바로 DRF의 Serializer 클래스임!
      • 그래서 Serializer가 Form이나 Model과 비슷하게 보이는 것임!
    • 과연 Serialization이 무엇이길래 Form, Model, Serializer 클래스에 공통적으로 제공되는 것일까?
  • Serialization을 이해하기 위한 두 가지 상황
    1. 메모리 내부와 외부 환경의 차이
      • 프로그램을 실행한다는 것 == 메모리 내부 환경에서 동작
      • 프로그램에서 어떤 값을 메모리 외부(=파일, DB, 네트워크)로 보낸다고 했을 때, 메모리 내부와 외부의 환경이 다르기 때문에 프로그램에서 사용되는 값을 그대로 보낼 수가 없음!
      • Serialize에 가장 많이 사용되는 포맷 : JSON
        • JSON에서 허용하는 TYPE : Number, String, Object, List, Null
    2. 복원시(=직렬화 후 역직렬화) 정보 유지 필요성

DRF에서 직렬화/역직렬화하는 과정

  • DRF에서 직렬화(serialize)하는 과정

    • Restframework 문서 내용
    • django shell에서 실습
      • ①data from DB
        >>> from api2.serializers import *
        >>> CommentSerializer()
        CommentSerializer():
            id = IntegerField(label='ID', read_only=True)
            content = CharField(label='CONTENT', style={'base_template': 'textarea.html'})
            create_dt = DateTimeField(label='CREATE DT', read_only=True)
            update_dt = DateTimeField(label='UPDATE DT', read_only=True)
            post = PrimaryKeyRelatedField(allow_null=True, queryset=Post.objects.all(), required=False)
        >>>
        >>> from blog.models import *
        >>> Comment.objects.all()
        <QuerySet [<Comment: 첫번째 댓글 입니다>, <Comment: 두번째 댓글 입니다>, <Comment: 마스크 벗고 운동하>, <Comment: 댓글 생성 테스트 >, <Comment: 오랜만에 나이키 >, <Comment: CSRF Token>, <Comment: ensure_csr>, <Comment: API  보기 >, <Comment: DRF로 댓글 생성>, <Comment: CreateAPIV>]>
        >>>
        >>> c0 = Comment.objects.all()[0]
        -> c0라는 레코드 객체는 DRF에서 'instance'라고 함!
      • ②serialize (instance -> dict) : instance 인자에 넣기!
        // CommentSerializer(instance=, data=)
        // serialize할 때는 instance 인자에 넣음!
        >>> sr = CommentSerializer(instance=c0)
        // serializer의 data 속성 확인 (-> dict 자료형으로 변환됨)
        >>> sr.data
        {'id': 1, 'content': '첫번째 댓글 입니다.', 'create_dt': '2021-07-28T07:06:54.974180', 'update_dt': '2021-07-28T07:06:54.974180', 'post': 3}
        // serializer의 data 속성의 Type 확인(ReturnDict -> DRF에서 기능을 추가한 dict 자료형)
        >>> data0 = sr.data
        >>> type(data0)
        <class 'rest_framework.utils.serializer_helpers.ReturnDict'>
      • ③response (최종적으로 ByteString 타입으로 만들어서 client에게 보내주려고 함, dict -> ByteString) : JSONRenderer 사용!
        
        >>> from rest_framework.renderers import JSONRenderer
        >>> JSONRenderer().render(data0) 
        b'{"id":1,"content":"\xec\xb2\xab\xeb\xb2\x88\xec\xa7\xb8 \xeb\x8c\x93\xea\xb8\x80 \xec\x9e\x85\xeb\x8b\x88\xeb\x8b\xa4.","create_dt":"2021-07-28T07:06:54.974180","update_dt":"2021-07-28T07:06:54.974180","post":3}'
        >>> json0 = JSONRenderer().render(data0)
        >>> type(json0)
        <class 'bytes'>
          -> ByteString이지만 한글이어서 16진수로 보임!
          -> JSON 포맷으로 변환된 ByteString임!
      • 정리 :c0이라는 인스턴스(=레코드 객체)dict로 변환한 후, 최종적으로 ByteString으로 변환하여 client에게 보냄 → serialize 과정
  • DRF에서 역직렬화(deserialize)하는 과정

    • Restframework 문서 내용
    • django shell 실습
      • ①requset (ByteString으로 들어온 데이터를 dict 형태로 변환함, ByteString -> dict) : JSONParserBytesIO클래스 이용!
        >>> from rest_framework.parsers import JSONParser
        // 주의! : JSONParser.parse() 메서드에 데이터를 직접 넣으면 에러 발생!
        >>> JSONParser().parse(json0)
        Traceback (most recent call last):
          File "<console>", line 1, in <module>
          File "C:\drf-core\.venv\lib\site-packages\rest_framework\parsers.py", line 65, in parse
            return json.load(decoded_stream, parse_constant=parse_constant)
          File "C:\drf-core\.venv\lib\site-packages\rest_framework\utils\json.py", line 31, in load
            return json.load(*args, **kwargs)
          File "C:\Users\Jinhyung\anaconda3\lib\json\__init__.py", line 293, in load
            return loads(fp.read(),
          File "C:\Users\Jinhyung\anaconda3\lib\codecs.py", line 496, in read
            newdata = self.stream.read()
        AttributeError: 'bytes' object has no attribute 'read'
        >>>
        // BytesIO(json0) : ByteString 데이터를 JSON 데이터로 변경
        >>> from io import BytesIO
        >>> JSONParser().parse(BytesIO(json0))
        {'id': 1, 'content': '첫번째 댓글 입니다.', 'create_dt': '2021-07-28T07:06:54.974180', 'update_dt': '2021-07-28T07:06:54.974180', 'post': 3}
        >>>
        >>> ddata0 = JSONParser().parse(BytesIO(json0))
        >>> type(ddata0)
        <class 'dict'>
      • ②-1 deserialize (dict -> instance) : data 인자에 넣기!
        // CommentSerializer(instance=, data=)
        // 역직렬화(deserialize) 과정에서는 data 인자에 넣음!
        >>> CommentSerializer(data=ddata0)
        CommentSerializer(data={'id': 1, 'content': '첫번째 댓글 입니다.', 'create_dt': '2021-07-28T07:06:54.974180', 'update_dt': '2021-07-28T07:06:54.974180', 'post': 3}):
            id = IntegerField(label='ID', read_only=True)
            content = CharField(label='CONTENT', style={'base_template': 'textarea.html'})
            create_dt = DateTimeField(label='CREATE DT', read_only=True)
            update_dt = DateTimeField(label='UPDATE DT', read_only=True)
            post = PrimaryKeyRelatedField(allow_null=True, queryset=Post.objects.all(), required=False)
      • ②-2 유효성 검사 : 사용자로부터 입력받은 데이터에는 오류가 있을 수 있으므로 반드시 유효성 검사 필요! : is_valid() 메서드 사용
        >>> dsr = CommentSerializer(data=ddata0)
        >>> dsr.is_valid()
        True
        // 만약 유효성 검사에 통과하지 못하여 False가 리턴되면, 해당 값이 errors라는 dict 자료형에 담기게 됨!
        >>> dsr.errors
        {}
        // 유효성 검사를 통과(True)한 데이터는 validated_data 속성에 담김
        // DB에 넣을 instance를 만들 때는, 이 validated_data를 넣어서 만듦!
        >>> dsr.validated_data
        OrderedDict([('content', '첫번째 댓글 입니다.'), ('post', <Post: 유럽 여행  파리 개선문 다녀 왔답니다.>)])
      • ③ Create instance & save to DB
        // validated_data로 인스턴스 생성
        >>> instance = Comment(**dsr.validated_data)
        // 생성된 instance를 DB에 저장
        >>> instance.save()
        >>>
        // check
        >>> Comment.objects.all()
        <QuerySet [<Comment: 첫번째 댓글 입니다>, <Comment: 두번째 댓글 입니다>, <Comment: 마스크 벗고 운동하>, <Comment: 댓글 생성 테스트 >, <Comment: 오랜만에 나이키 >, <Comment: CSRF Token>, <Comment: ensure_csr>, <Comment: API  보기 >, <Comment: DRF로 댓글 생성>, <Comment: CreateAPIV>, <Comment: 첫번째 댓글 입니다>]>
      • 정리
        • ①client로 받은 ByteString을 JSON으로 변경하고 그것을 dict 자료형으로 만듦
        • ②그렇게 만든 dict 자료형 데이터를 바로 사용하는 것이 아니라, serializer를 통해서 is_valid() 메서드를 호출해서 validated_data를 만든 뒤에 validated_data를 이용하여 instance를 생성함
        • ③ 그리고 save() 메서드를 사용하여 instance를 DB에 저장함
  • Serialize vs Deserialize 과정 비교

    • Serialize
      • 1) instance
      • 2) S(instance=xxx)
      • 3) dict
      • 4) json data
      • 5) response
    • Deserialize
      • 1) json data
      • 2) dict
      • 3) S(data=xxx)
      • 4) is_valid(), validated_data
      • 5) instance
      • 6) save() (= 내부적으로 create()/update() 메서드 호출)
    • 정리⭐
      • DRF에서는 Serializer 하나로 직렬화/역직렬화 기능을 모두 수행함
      • Serializer에는 방향성이 존재하기 때문에, 방향성에 맞춰서 주의해서 사용해야 함! (사용하는 인자(instance/data)가 다르고, is_valid() 메서드 사용 유무가 다름)
      • Serializer 입장에서 직렬화(serialize)과정read operation, 역직렬화(deserialize)과정write operation이라고 함!
        • serialize(=read)과정 : GET 메서드 처리
        • deserialize(=write)과정 : POST, UPDATE, DELETE, PATCH 메서드 처리

Reference

https://www.inflearn.com/course/%EC%9E%A5%EA%B3%A0-drf/dashboard

profile
기록하는 습관

0개의 댓글