코드로 우주평화
Python JSON 직렬화 TypeError 핸들링하기 본문
json 변환 시 타입에러 발생
아래와 같이 Decimal 타입을 json 으로 변환하는 코드가 있다고 해보자.
import json
from decimal import Decimal
data = {"value": Decimal("123.456")}
json_str = json.dumps(data)
print(json_str)
이를 실행하면 다음의 에러가 발생한다.
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Decimal is not JSON serializable
왜냐하면 json
라이브러리는 기본적으로 다음 타입들만 직렬화를 지원하기 때문이다.
dict
list
,tuple
str
int
,float
,bool
None
이 문제를 해결하기 위해 다음처럼 코드를 수정할 수 있을 것이다.
import json
from decimal import Decimal
data = {"value": str(Decimal("123.456"))} # Decimal 을 str 로 바꾼다.
json_str = json.dumps(data)
print(json_str)
# {"value": "123.456"}
문제는 해결하였지만 실제 데이터의 구조는 더 복잡하고 어떤 값이 Decimal 타입인지 모두 확인하여 수정해야하는 번거로움이 생긴다.
다행스럽게도 json 은 default 파라미터를 두어 이를 쉽게 해결할 수 있도록 지원한다.
default 파라미터 넣기
Python의 json 은 default 파라미터를 통해 커스텀 타입을 어떻게 직렬화할지 지정할 수 있다.
예를 들어, Python의 Decimal
타입은 기본적으로 JSON으로 직렬화가 안되지만, default
파라미터에 함수를 지정해주면 그 함수 내에서 Decimal
을 문자열로 변환해주는 등의 처리를 할 수 있다.
def default(obj):
if isinstance(obj, Decimal):
return str(obj)
raise TypeError
data = {"value": Decimal("123.456")}
json_str = json.dumps(data, default=default) # default 파라미터를 추가
print(json_str)
# {"value": "123.456"}
예제 코드를 보면, default
함수 내에서 isinstance(obj, Decimal)
로 체크를 해서 만약 그게 True
면 str(obj)
로 바꿔서 리턴해준다.
즉, json.dumps()
함수를 호출하면 Decimal
타입이 문자열로 바뀌어서 JSON 직렬화가 되는 원리이다.
추가로 default
함수를 보면 raise TypeError
이 있는데 이는 Decimal
타입이 아닌 set
이나 datetime
, date
등의 json 이 지원하지 않는 다른 타입이 오면 에러를 발생시키는 부분이다. 만약 raise 를 하지 않고 None
을 리턴한다면 json 은 이를 정상 값으로 인식해서 "null"로 직렬화한다.
def default(obj):
if isinstance(obj, Decimal):
return str(obj)
return None
data = {"value": datetime.now()} # value 는 현재시간이어야 한다.
json_str = json.dumps(data, default=default)
print(json_str)
# {"value": null} # 하지만 null 로 반환된다.
참고로, default
로 넘어오는 obj
는 Python의 json
라이브러리가 JSON으로 직렬화할 수 없는 객체 타입만 넘어온다. 그러니 str
, dict
, list
같은 기본 타입들까지 default
함수에서 설정해줄 필요는 없다.
default 직렬화 응용하기
default
함수를 활용하면 json 직렬화(변환) 과정에서 추가적인 로직을 수행하는 등의 응용도 할 수 있다. 예를 들어 datetime
객체를 timestamp
형식으로 바꾸어 값을 변환할수도 있다.
def default(obj: Any):
if isinstance(obj, datetime):
return obj.timestamp()
else:
return "This object cannot be serialized."
data = {"value1": datetime.now(), "value2": uuid.uuid4()}
json_str = json.dumps(data, default=default)
print(json_str)
# {"value1": 1695709237.002437, "value2": "This object cannot be serialized."}
또한 어떠한 경우라도 에러를 발생시키지 않아야하는 상황이라면 return "This object cannot be serialized."
처럼 해당 값에 특정 문자열을 저장 시킬 수 있다.
즉, 개발 요구사항에 따라 다양하게 default 옵션을 응용하여 사용할 수 있다.
이 밖에도 json 은 다양한 옵션 파라미터를 지원하니 더 자세히 알고 싶다면 공식문서를 참고하기 바란다.
'나는 이렇게 학습한다 > Library' 카테고리의 다른 글
Pydantic 옵션 하나로 ORM 을 DTO 모델로 변환하기 (0) | 2023.08.22 |
---|---|
Pydantic 의 Datetime 타입으로 날짜와 시간을 다뤄보자 (0) | 2023.08.21 |
requests _ response 상태에 따라 raise를 할 수 있다? (0) | 2022.10.18 |
파이썬으로 해외 증권거래소 개장일/휴장일 확인하는 방법 (0) | 2022.08.30 |
Tenacity _ 예외가 발생한 함수를 다시 실행하려면? (0) | 2022.08.27 |