Python 에서 JSON 사용하기

2023. 12. 10. 22:30Python

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

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

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

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 을 다룰 때 뚝딱뚝딱 거릴 수 있겠어요.

날로 먹으려 하지 말고 문서를 보도록 해요...

그럼 안녕


참조:

 
 
 
 

 

'Python' 카테고리의 다른 글

pyenv 설정하기  (0) 2023.10.25