JSON 형식에는 튜플(tuple)에 해당하는 자료형이 존재하지 않습니다. 그렇기 때문에 Python의 json 모듈은 튜플을 직렬화할 때 JSON에서 튜플과 가장 유사한 형태인 배열(list)로 변환합니다.
여기서 중요한 문제는 이 과정에서 튜플의 핵심 특성인 불변성(immutability)이 사라진다는 점입니다. 역직렬화된 결과는 일반 리스트로 복원되며, 원래 튜플이었는지 여부를 알 수 없게 됩니다.
튜플을 보존하는 두 가지 방법
1. Pickle 사용하기
Python의 pickle 모듈을 사용하면 튜플의 자료형과 불변성을 그대로 유지할 수 있습니다. 다만 pickle은 데이터를 .pkl 파일로 저장하기 때문에 JSON 파일에는 저장할 수 없습니다. 따라서 웹을 통해 데이터를 주고받는 상황에서는 적합하지 않습니다. 또한 pickle은 역직렬화 시 임의 코드 실행 위험이 있으므로, 반드시 신뢰할 수 있는 소스의 데이터에만 사용해야 합니다.
2. 커스텀 인코더/디코더 작성하기
웹 환경에서 가장 권장되는 방법은 자신만의 인코더와 디코더를 작성하여 리스트와 튜플을 구분하는 것입니다. 예를 들어 튜플을 특별한 마커가 포함된 JSON 객체로 변환하면 됩니다.
import json
class TupleEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, tuple):
return {'__type__': 'tuple', 'items': list(obj)}
return super().default(obj)
def as_tuple(dct):
if dct.get('__type__') == 'tuple':
return tuple(dct['items'])
return dct
data = {'point': (1, 2), 'values': [3, 4]}
encoded = json.dumps(data, cls=TupleEncoder)
decoded = json.loads(encoded, object_hook=as_tuple)
print(decoded['point']) # (1, 2) — 튜플로 복원됨
print(type(decoded['point'])) # <class 'tuple'>
이 방식은 인코더와 디코더를 어떻게 설계하느냐에 따라 리스트와 튜플을 자유롭게 구분할 수 있으며, JSON 형식을 유지한 채 웹 전송에도 문제없이 사용할 수 있다는 큰 장점이 있습니다.
정리
- JSON에는 튜플 개념이 없기 때문에 Python 튜플은 자동으로 배열로 변환되며 불변성은 유지되지 않습니다.
- 불변성을 유지하려면 pickle(로컬 파일 저장용) 또는 커스텀 인코더/디코더(웹 전송용)를 활용하세요.
- 웹 환경에서 데이터를 주고받아야 한다면 커스텀 인코더/디코더가 가장 실용적인 선택입니다.