Python
Python 에서 JSON 사용하기
kingsubin
2023. 12. 10. 22:30
Python 에서 JSON 사용하기
얼마 전 Python 에서 JSON 을 다뤄야 하는 문제가 있었는데 문서는 안 읽고 매번 똑같은 커맨드 사용했더니 문제를 못 풀었어요.
현타 오고 바보가 된 것 같아서 문서를 쭉 읽어보기로 해요.
fp 를 다루는 dump, load 는 비슷하니, 더 자주 쓸 것 같은 dumps, loads 로 알아볼게요.
json.dumps()
def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
default=None, sort_keys=False, **kw)
변환표
- 파이썬 -> JSON 변환이 어떻게 되는지 알아봐요.
파이썬 | JSON |
---|---|
dict | 오브젝트(object) |
list, tuple | 배열(array) |
str | 문자열(string) |
int, float, int와 float에서 파생된 열거형 | 숫자(number) |
True | true |
False | false |
None | null |
skipkeys=False
True
설정 시 기본형(str, int, float, bool, None)이 아닌 딕셔너리 키는 TypeError 를 발생시키지 않고 건너뛰어요.- 딕셔너리 키는 immutable 한 값이어야 하는데 그러면 위의 기본형을 제외하고는 tuple 밖에 생각이 안나네요.
- dict key 값에 tuple 이 있을 때 사용하면 될 것 같아요.
data = {"name": "kingsubin", "city": "busan", ('tuple_key', ): "soob"}
json.dumps(data)
# TypeError: keys must be str, int, float, bool or None, not tuple
json.dumps(data, skipkeys=True)
# {"name": "kingsubin", "city": "busan"}
ensure_ascii=True
False
설정 시 비 아스키 문자들을 그대로 출력해 줘요.
data = {"이름": "수빈", "city": "부산", "pc": "mac"}
json.dumps(data)
# {"\uc774\ub984": "\uc218\ube48", "city": "\ubd80\uc0b0", "pc": "mac"}
json.dumps(data, ensure_ascii=False)
# {"이름": "수빈", "city": "부산", "pc": "mac"}
check_circular=True
- 순환 참조를 검사할지 여부예요.
- 이걸 False 로 설정하고 쓸 일이 있을지 잘 모르겠어요.
data = {}
data["self"] = data
json.dumps(data)
# ValueError: Circular reference detected
json.dumps(data, check_circular=False)
# RecursionError: maximum recursion depth exceeded while encoding a JSON object
allow_nan=True
- JSON 범위를 벗어난 float 값(nan, inf, -inf)을 직렬화하면 ValueError 를 발생시켜요.
- JSON https://www.json.org/json-en.html 을 보면 NaN, Infinity, -Infinity 는 없는데 JavaScript equivalents (
NaN
,Infinity
,-Infinity
) 를 사용한다고 해요. - https://github.com/python/cpython/blob/8b58d12f6ccc654389976661d193361da932d96a/Lib/json/encoder.py#L35 INFINITY 를 정의해놓고 쓰고 있어요.
- nan, inf, -inf 를 막고 싶을 때 쓰려나 싶은데 자주 쓰진 않을 것 같아요.
data = {"nan": math.nan, "inf": float("inf"), "-inf": float("-inf")}
json.dumps(data)
# {"nan": NaN, "inf": Infinity, "-inf": -Infinity}
json.dumps(data, allow_nan=False)
# ValueError: Out of range float values are not JSON compliant
cls=None
- type[JSONEncoder] 을 받아요.
- 기본값으로는
json.JSONEncoder
를 사용하는데 특정한 객체를 JSON 으로 변환하는 방법을 정의하고 싶을 때 사용해요. - 많이 쓰이면서 serializable 하지 않은 set, datetime 같은 타입을 위해 커스텀인코더를 만들어 두고 쓰면 좋을 것 같은 생각이 들어요.
data = {
"name": "kingsubin",
"cities": ["seoul", "busan"],
"nickname": "soob",
"timestamp": datetime.now(),
}
class KingsubinEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
elif isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
json.dumps(data, cls=KingsubinEncoder)
# {"name": "kingsubin", "cities": ["seoul", "busan"], "nickname": "soob", "timestamp": "2023-12-08T23:39:09.835149"}
indent=None
- 음수나 ""는 줄 넘김만 삽입하고, 양의 정수를 사용하면 레벨당 그만큼의 스페이스를 들여 쓰기 해요.
- 새로운 라인을 어떻게 만드는지 궁금하면 코드를 쭉 타고 봐야 할 것 같아요.
- https://github.com/python/cpython/blob/8b58d12f6ccc654389976661d193361da932d96a/Lib/json/encoder.py#L260C5-L260C21
data = {"name": "kingsubin", "city": "busan", "nickname": "soob"}
json.dumps(data)
# {"name": "kingsubin", "city": "busan", "nickname": "soob"}
json.dumps(data, indent=0)
"""
{
"name": "kingsubin",
"city": "busan",
"nickname": "soob"
}
"""
json.dumps(data, indent="umm")
"""
{
umm"name": "kingsubin",
umm"city": "busan",
umm"nickname": "soob"
}
"""
separators=None
- tuple[str, str] 타입을 받아요. (item_separator, key_separator)
- 기본값은
(', ', ': ')
이어서, 각 separator 뒤에 한 칸씩 공백이 있어요. - 공백을 줄이고 싶으면
(',', ':')
이렇게 뒤에 공백을 제거하면 되겠어요.
data = {"name": "kingsubin", "city": "busan", "nickname": "soob"}
json.dumps(data)
# {"name": "kingsubin", "city": "busan", "nickname": "soob"}
json.dumps(data, separators=(",", ":"))
# {"name":"kingsubin","city":"busan","nickname":"soob"}
json.dumps(data, separators=("item", "key"))
# {"name"key"kingsubin"item"city"key"busan"item"nickname"key"soob"}
default=None
- 직렬화할 수 없는 객체에 대해 호출되는 함수예요.
- 예시로 많이 쓰이는 직렬화 할 수 없는 datetime 객체를 다뤄봐요.
data = {
"name": "kingsubin",
"city": "busan",
"nickname": "soob",
"timestamp": datetime.now(),
}
def serialize_datetime(object):
if isinstance(object, datetime):
return object.isoformat()
return None
json.dumps(data)
# TypeError: Object of type datetime is not JSON serializable
json.dumps(data, default=serialize_datetime)
# {"name": "kingsubin", "city": "busan", "nickname": "soob", "timestamp": "2023-12-08T23:29:59.819621"}
sort_keys=False
True
설정 시 JSON 객체의 키를sorted()
를 사용해서 정렬해요.- key 에 str, int 가 섞여 있으면 정렬하지 못해요.
data = {"name": "kingsubin", "city": "busan", "nickname": "soob"}
json.dumps(data)
# {"name": "kingsubin", "city": "busan", "nickname": "soob"}
json.dumps(data, sort_keys=True)
# {"city": "busan", "name": "kingsubin", "nickname": "soob"}
data = {3: "kingsubin", 1: "busan", "nickname": "soob"}
json.dumps(data, sort_keys=True)
# TypeError: '<' not supported between instances of 'str' and 'int'
json.loads()
def loads(s, *, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
변환표
- JSON -> Python 변환이 어떻게 되는지 알아봐요.
JSON | 파이썬 |
---|---|
오브젝트(object) | dict |
배열(array) | list |
문자열(string) | str |
숫자 (정수) | int |
숫자 (실수) | float |
true | True |
false | False |
null | None |
cls=None
- json.dumps() 에서 설명한 cls 와 같아요.
object_hook=None
- dict 대신 다른 객체를 만들 때 호출되는 함수를 지정하는 옵션이에요.
- custom decoders 를 구현하는 데 사용할 수 있어요.
- 예시로 json_string 에 datetime isoformat 이 존재하면 datetime 으로 바꿔주는 custom_hook 을 만들어봐요.
json_string = '{"name": "kingsubin", "cities": ["seoul", "busan"], "nickname": "soob", "timestamp": "2023-12-08T23:54:18.839402"}'
def custom_hook(dct):
for key, value in dct.items():
if isinstance(value, str):
try:
dct[key] = datetime.fromisoformat(value)
except ValueError:
pass
return dct
json.loads(json_string, object_hook=custom_object_hook)
# {'name': 'kingsubin', 'cities': ['seoul', 'busan'], 'nickname': 'soob', 'timestamp': datetime.datetime(2023, 12, 8, 23, 54, 18, 839402)}
parse_float=None
- 디코딩될 모든 JSON float 에 대해서 호출돼요.
json_string = '{"name": "subin", "age": 30, "city": "busan", weight: 64.123}'
def custom_float_parser(weight):
return rount(float(weight), 2)
json.loads(json-string, parse_float=custom_float_parser)
# {'name': 'subin', 'age': 30, 'city': 'busan', 'weight': 64.12}
parse_int=None
- 디코딩 될 모든 JSON int 에 대해서 호출돼요.
- parse_float 과 같이 쓰면 될 것 같은데 적절히 쓰일만한 예시 생각이 안 나요.
parse_constant=None
- 디코딩 될
'-Infinity'
,'Infinity'
,'NaN'
에 대해서 호출돼요. - 자주 쓰이진 않을 것 같아요.
json_string = '{"inf": Infinity, "neginf": -Infinity, "nan": NaN}'
def custom_constant_parser(x):
if x == "Infinity":
return "hello"
elif x == "-Infinity":
return "hello2"
elif x == "NaN":
return "hello3"
return x
json.loads(json_string)
# {'inf': inf, 'neginf': -inf, 'nan': nan}
json.loads(json_string, parse_constant=custom_constant_parser)
# {'inf': 'hello', 'neginf': 'hello2', 'nan': 'hello3'}
object_pairs_hook=None
- object_hook 과 유사하지만, dict 대신 (key, value) 이터레이터를 받아요.
- object_hook 이 정의되어 있으면 object_pairs_hook 이 우선순위를 가져요.
json_string = '{"NAME": "subin", "City": "busan"}'
def custom_pairs_hook(pairs):
result = {}
for key, value in pairs:
result[key.lower()] = value
return result
json.loads(json_string)
# {'NAME': 'subin', 'Age': 30, 'City': 'Busan'}
json.loads(json_string, object_pairs_hook=custom_pairs_hook)
# {'name': 'subin', 'age': 30, 'city': 'Busan'}
한 번 봤으니 이제는 Python 으로 JSON 을 다룰 때 뚝딱뚝딱 거릴 수 있겠어요.
날로 먹으려 하지 말고 문서를 보도록 해요...
그럼 안녕

참조:
- https://json.org/
- https://docs.python.org/ko/3.12/library/json.html#
- https://github.com/python/cpython/blob/3.12/Lib/json/__init__.py