JSON Serializer
Python 을 다루면서 프로젝트를 하게 되면 JSON을 Binary 형태로 변환하거나, 혹은 그 반대의 작업을 해야 하는 경우는 필연적으로 발생하게 됩니다. 기본적인 타입(Primitive type
)들은 자연스럽게 변환을 할 수 있지만, 우리가 실제로 작성하게 되는 클래스들은 그렇지 못한 경우가 대부분 입니다.
이번 게시글에서는 Python이 어떤 과정을 통해서 타입을 JSON 형태로 변환하게 되는 지 살펴보려고 합니다.
JSON 모듈
Python 자체에 내재되어 있으며, 가장 많이 사용되는 json
모듈입니다. 주된 사용법은 이렇습니다.
Serialize
Deserialize
json.dumps
내부에서는 어떻게 데이터들을 처리하고 있을까요?
먼저 내부 코드를 살펴보면 기본적으로 JSONEncoder
를 호출하고 있음을 살펴볼 수 있습니다.
그럼 이 JSONEncoder
는 정확히 어떻게 동작하는 걸까요?
똑같이 내부 코드를 타고타고 들어가 핵심 변환 로직은 이렇게 작성되어 있습니다.
None
과boolean
타입은 단순하게 보이실겁니다.int
타입은_intstr=int.__repr__
로 정의되어 있어__repr__
를 참조하고 있습니다.float
은 별도 정의된 함수 에서 처리하며 마찬가지로__repr__
를 참조하고 있습니다.list
와tuple
은 요소를 순회하며 각각의 타입마다 똑같이__repr__
를 참조하고 있습니다.dict
도 마찬가지로 각각의 요소를 순회 하며 각각의 타입마다__repr__
를 참조하고 있습니다.
한가지 더 중요하게 봐야하는 점은, else
구문입니다.
코드에 마지막에보면 new_obj = _default(o)
라고 해서 _default
메소드를 호출하는데, 이 메소드는 json.dumps
의 매개변수에 주입할 수 있는 메소드입니다. JSONEncoder의 default 메소드의 설명을 보면 상속 받아서 타입에 적합한 Serialize 방식을 새로 정의하라고 적혀있습니다.
요약을 하자면, json
모듈은 기본 자료형에 대해서만 Serialization 을 지원합니다.
- 호환되는 기본 자료형: bool, None, int, float, list, tuple, dict
- 호환되지 않는 자료형은 먼저
default()
메소드를 호출하여 시도합니다. Enum
은 기본적으로 지원하지 않습니다.datetime
또한 지원하지 않습니다.
DjangoJSONEncoder
Django나 DRF 에서 가장 기본으로 사용하는 Encoder
입니다.
내부 코드를 보면 json.JSONEncoder
를 상속받고 default()
메소드를 간단하게 구현한 것을 확인할 수 있습니다. 해당 코드를 살짝 살펴 보면 datetime
에 대해서 추가적인 Serialize 방법을 제공하고 있음을 확인하실 수 있습니다. 그리고 해당되는 타입이 없다면 super().default(o)
를 통해서 상위 메소드에 변환을 위임하고 있네요.
요약하자면 다음과 같습니다.
- 기본 자료형은
JSONEncoder
에서 처리합니다. datetime
은_default
를 통해서 처리되는데,DjangoJSONEncoder
가 이를 구현했습니다.- 모델 정의 및 쿼리에 사용되는
BaseModel
이나Queryset
은 지원되지 않습니다. - 마찬가지로
Enum
은 지원되지 않습니다.
DRF JSONEncoder
DRF은 Django의 확장판이라고도 불리고, 사실상 요즘은 DRF로 많이 사용되고 있는 것 같습니다.
여기도 마찬가지로 내부 코드 를 보시면 Django
보다 더 다양한 타입들을 직렬화 하는 것을 보실 수 있습니다.
대표적으로 추가되는 항목들은 다음과 같습니다.
보시면 QuerySet
부터, bytes
심지어 메소드를 인식하고 적절한 메소드를 호출하는 로직까지 들어가 있습니다.
Ninja JSONEncoder
Ninja는 Django의 또 다른 버전입니다. Ninja 는 내부적으로 Pydantic
을 이용하는데, Ninja 에서는 Pydanitc
에 대한 Serialize 수단을 추가로 제공합니다.
마찬가지로 내부 코드를 보면 BaseModel
에 대한 처리가 되어 있는 것을 확인하실 수 있습니다.
Ninja
는 Enum
을 같이 처리하고 있습니다. 하지만 단순히 str
처리만 하고 있기 때문에 직렬화 수단으로는 부족한 면이 있습니다.
정리
flowchart TD
A[json.dumps] --> B["is (None, bool, int, str, float, list, tuple, dict)"]
B -->|Yes| C[__repr__를 통한 변환]
C -->END
B -->|No| D["주입된 default() 메소드 호출"]
D -->|DjangoJSONEncoder|END
D -->|DRFJSONEncoder|END
D -->|NinjaJSONEncoder|END
D -->|...etc|END