[python] JSON, JSON 모듈
A A

목차

    728x90

    JSON?

    JSON은 어떤 프로그래밍 언어를 사용했는지 중요하지 않다.

    겉보기에는 호환되지 않는 것 처럼 보이는 당사자들 간의 데이터의 이동을 돕는 일종의 범용 브리지이다.

    JSON은 객체 또는 객체 집합의 내용인 데이터를 전송해야하는 아주 기본적 요구에 대한 해답으로,

    이 문제를 해결하는 메커니즘은 이식성이 뛰어나고 플랫폼에 독립적이여야한다.

     

    우리가 해결해야할 문제는 네트워크 전송, 플랫폼 간의 변환을 견딜수 있는 방식으로 객체 또는 단일 값을 표현하는 방법이다.

    JSON은 해당 문제를 두가지 간단한 방법을 이용하여 해결한다.

    • UTF-8로 코딩된 텍스트를 사용한다.
      즉 기계/플랫폼에 종속적인 형식이 아니고, 또한 사람이 잘 읽을 수 있는 형식을 의미한다.
    • 객체의 서로 다른 부분간의 상호 종속성과 관계를 표현하기 위해 간단하고 확장성이 높지 않는 형식을 사용한다.
      이를 통해 객체 속성 값 뿐만 아니라 이름도 전송할 수 있다.

    JSON에서는 숫자, 문자, bool, 아무것도 없는 이름의 값을 가질 수 있는데 이것이 JSON의 가장 큰 장점이라고 하긴 어렵다.

    JSON은 더 큰 단위로 수집 및 집계된 훨씬 더 복잡한 데이터를 전달할 수 있다.

    파이썬의 딕셔너리와 유사한 구문을 제공하는데 실제로 key: value의 쌍의 집합으로 구성된다.

     

    그렇다면 파이썬 구문을 이용하여 REST 내의 네트워크 메세지를 코딩과 디코딩할 수 있을까 ?

    당연히.

    파이썬개발자 뿐만아니라 데이터를 이해하고 싶다면 JSON을 사용해야한다.

    아주 간단한 예제 부터 살피면 다음과 같은 형식이다.

    { "apple" : 11 }

     

    더 자세한 사항은 아래 링크를 확인하자.

    https://ecma-international.org/publications-and-standards/standards/ecma-404/

     

    ECMA-404 - Ecma International

    The JSON data interchange syntax - JSON defines a small set of structuring rules for the portable representation of structured data

    ecma-international.org

     

    * 표기 주의사항 및 특징

     

    • JSON 에서는 텍스트 안에 음수를 입력하려면 빼기 기호를 사용해도 된다.
      그러나 양수임을 표기하기 위해 더하기 기호를 사용하지말것.
      또한 앞에 0을 표기하지 말것.
    • JSON의 실수는 과학적 표기법을 사용하는 것을 포함하여 파이썬 실수와 동일하다.
      • 3.0857E16
    • 문자열 안에 따옴표를 그냥 삽입할 수 없으므로 백슬래시(\)를 사용할 것.
      • "\"Python\""
    • JSON 문자열은 여러 줄로 나눌 수 없고 한 줄에 \n 통해 여러줄 을 표기할 수 있다.
    • bool 값은 true / false 로 표기한다.
    • 리터럴의 경우 대소문자를 구분한다.
    • 값이 없거나 의미가 없는 값을 나타내기 위해 파이썬은 None을 사용하지만, JSON에서는 null로 표기한다.
    • 위의 값들을 두가지 방법으로 결합 또는 압축이 가능하다.
      • 배열(Python 리스트와 유사)
      • 객체 (Python 딕셔너리와 유사)
    • 속성 이름에는 제한이 없고, 변수처럼 고유성이 필요한 것이 아니기 때문에 고유할 필요가 없다.
    • 특정 객체나 빈 배열을 표기하기 위해 space를 넣는다.
      • [ ]
      • { }
    • 공백(tab 포함)을 무시하므로 원하는 방식으로 텍스트  서식을 지정할 수 있다.
      • 공백과 줄내림이 많은 경우 사람이 읽기 쉽고,
      • 한줄에 쓰는 경우 점유하는 바이트 수가 적기 때문에 전송 비용이 저렴하다.
    • 하나의 객체안에 여러 유형의 데이터를 통합할 수 있다.

    python에서 JSON 모듈 사용하기

     

    1. 모듈 가져오기

    import json

    JSON 모듈을 통해 복잡한 JSON문을 해결할 수 있다.

     

    2. json.dumps() : 파이썬 데이터 -> JSON 문자열

    Python 데이터를 JSON 문자열로 자동 변환하는 기능을 dumps() 함수를 사용하여 해결할 수 있다.

    a. 복잡한 단일 문자

    import json
    
    comics = '"The Meaning of Life" by Monty Python\'s Flying Circus'
    json_comics = json.dumps(comics)
    print(json_comics)
    "\"The Meaning of Life\" by Monty Python's Flying Circus"

     해당 결과와 같이 JSON 의 요구 사항을 자동으로 충족시켜준다.

     

    b. 리스트

    # list
    import json
    
    my_list = [1, 2.34, True, "False", None, ['a', 0]]
    json_list = json.dumps(my_list)
    print(json_list)
    [1, 2.34, true, "False", null, ["a", 0]]

    해당 결과를 보면 python의 True 가 JSON의 true로 변경, python의 None 이 JSON의 null 로 변경된 것을 볼 수 있다.

     

    c. 튜플

    import json
    
    my_tuple = (1, 2.34, True, "False", None, ['a', 0])
    json_tuple = json.dumps(my_tuple)
    print(json_tuple)
    [1, 2.34, true, "False", null, ["a", 0]]

    tuple 의 경우 JSON은 구별할 수 없어 리스트처럼 JSON 의 배열로 변환된다.

     

    d. 딕셔너리

    import json
    
    my_dict = {'a': 1, 'b': 2.34, 'c': True, 'd': "False", 'e': None, 'f': ['a', 0]}
    json_dict = json.dumps(my_dict)
    print(json_dict)
    {"a": 1, "b": 2.34, "c": true, "d": "False", "e": null, "f": ["a", 0]}

    JSON의 형식이 python의 딕셔너리 형식과 매우 유사한 것을 확인할 수 있다.

     

    Python data JSON element
    dict object
    list / tuple array
    string string
    int / float number
    True / false true / false
    None null

     

    간단하면서도 일관성이 있어보이지만 주의할 점이 있다.

     

    e. 클래스 객체

    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    p = Person('Bob', 42)
    json_p = json.dumps(p)
    print(json_p)
    TypeError Traceback (most recent call last)
    /tmp/ipython-input-8-3535271981.py in <cell line: 0>()
               8
               9 p = Person('Bob', 42)
        ---> 10 json_p = json.dumps(p) 11 print(json_p)
    3 frames
    /usr/lib/python3.11/json/encoder.py in default(self, o)
              178
              179        """
         --> 180 raise TypeError(f'Object of type {o.__class__.__name__} '
              181 f'is not JSON serializable')
              182
    TypeError: Object of type Person is not JSON serializable

     

    • class 해결 방안 1

    객체 속성과 그 집합 외에 필요한 것이 없다면 객체 자체가 아닌 객체의 __dict__ 속성을 dumps() 하여 동작할 수 있다.

    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    p = Person('Bob', 42)
    json_p = json.dumps(p.__dict__)
    print(json_p)
    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def encode_person(o):
      if isinstance(o, Person):
        return o.__dict__
      else:
        raise TypeError(f'Object of type {o.__class__.__name__} is not JSON serializable')
    
    p = Person('Bob', 42)
    json_p = json.dumps(p, default=encode_person) #default를 통해 함수를 적용
    print(json_p)
    {"name": "Bob", "age": 42}

     

    파이썬 내부적으로 저장된 객체를 텍스트 또는 기타 이식 가능한 형태로 변환하는 과정을 직렬화라고 하고,

    반대의 동작의 경우 역직렬화라고 한다.

    json.dumps() 함수의 default인자는 직렬화(serialization) 과정에서 json 모듈이

    기본적으로 처리할 수 없는 사용자 정의 객체나 특정 타입의 객체를 만났을 때,

    어떻게 처리해야하는지 알려주는 역할을 수행한다.

    2025.06.26 - [Developer/Python] - [python] pickle 모듈을 이용한 객체 직렬화

     

    [python] pickle 모듈을 이용한 객체 직렬화

    with open('hello.pickle','rb') as f: data = pickle.load(f)print(type(data))print(data)data()Serialization , Deserialization , Pickle이번 글은 나중에 사용하기 위해 Python 객체를 저장하는 방법을 알아보고자 한다.피클링(Picklin

    radaonmommy.tistory.com

     

    • class 해결 방안 2

    두번째 접근 방식은 직렬화가 실제로 json.JSONEncoder 클래스 일부인 default() 메서드에 의해 수행된다는 사실을 기반으로 한다.

    이 방식을 사용하면 JSONEncoder 하위 클래스를 정의하는 메서드를 오버라이드하여 dumps() 메서드를 cls 라는 키워드 인수를 사용하여 전달한다.

    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    class Encoder(json.JSONEncoder):
      def default(self, o):
        if isinstance(o, Person):
          return o.__dict__
        else:
          return super().default(self, o)
    
    p = Person('Bob', 42)
    json_p = json.dumps(p, cls=Encoder)
    print(json_p)
    {"name": "Bob", "age": 42}

    dumps()는 기본 JSONEncoder 대신 우리가 지정한 Encoder 클래스를 사용하여 직렬화 과정을 수행하게 된다.

    이 Encoder 클래스는 오버로드된 default() 메서드를 가지고 있으므로,

    Person 객체를 만났을 때 우리가 정의한 방식으로 처리할 수 있게 된다.

     

    3. json.load() : JSON 문자열 -> 파이썬 데이터

    json 모듈의 load() 함수를 활용하여 Python 데이터로 변환하기

    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    class Encoder(json.JSONEncoder):
      def default(self, o):
        if isinstance(o, Person):
          return o.__dict__
        else:
          return super().default(self, o)
    
    print("====== Python -> JSON ======")
    p = Person('Bob', 42)
    json_p = json.dumps(p, cls=Encoder)
    print(json_p)
    print(f"type : {type(json_p)}")
    
    print("====== JSON -> Python ======")
    dict_p = json.loads(json_p)
    print(dict_p)
    print(f"type : {type(dict_p)}")
    ====== Python -> JSON ======
    {"name": "Bob", "age": 42}
    type : <class 'str'>
    ====== JSON -> Python ======
    {'name': 'Bob', 'age': 42}
    type : <class 'dict'>

    json.load() 함수로 인해 JSON의 문자열에서 Python의 딕셔너리로 제대로 변환된 것을 볼 수 있다.

     

    a. 복잡한 단일 문자

    이 함수를 통해 JSON 문자열과 같은 문자열도 처리하는 것을 볼 수 있다.

    import json
    
    
    strange_str = '"\\"The Meaning of Life\\" by Monty Python\'s Flying Circus"'
    print("load()전의 문자열 :",strange_str)
    
    json_load_str = json.loads(strange_str)
    print("load()후의 문자열 :",json_load_str)
    print(type(json_load_str))
    load()전의 문자열 : "\"The Meaning of Life\" by Monty Python's Flying Circus"
    load()후의 문자열 : "The Meaning of Life" by Monty Python's Flying Circus
    <class 'str'>

    백슬래쉬를 하나씩 제거하고나면 마음에 들지 않는 형태의 문자열이,

    json.load()를 통해 우리가 원하는 형태의 문구가 나오는 것을 확인할 수 있다.

     

    b. 리스트

    import json
    
    
    my_list = '[1, 2.34, true, "False", null, ["a", 0]]'
    print("load()전의 JSON 배열 문자열 :",my_list)
    
    json_load_list = json.loads(my_list)
    print("load()후의 Python 리스트 :",json_load_list)
    print(type(json_load_list))
    load()전의 JSON 배열 문자열 : [1, 2.34, true, "False", null, ["a", 0]]
    load()후의 Python 리스트 : [1, 2.34, True, 'False', None, ['a', 0]]
    <class 'list'>

    JSON형 문자열이 올바르게 처리된 것으로 확인할 수 있다.

     

    c. 딕셔너리

    import json
    
    
    my_dict = '{"a": 1, "b": 2.34, "c": true, "d": "False", "e": null, "f": ["a", 0]}'
    print("load()전의 JSON 딕셔너리 문자열 :",my_dict)
    
    json_load_dict = json.loads(my_dict)
    print("load()후의 Python 딕셔너리 :",json_load_dict)
    print(type(json_load_dict))
    load()전의 JSON 딕셔너리 문자열 : {"a": 1, "b": 2.34, "c": true, "d": "False", "e": null, "f": ["a", 0]}
    load()후의 Python 딕셔너리 : {'a': 1, 'b': 2.34, 'c': True, 'd': 'False', 'e': None, 'f': ['a', 0]}
    <class 'dict'>

    위와 같이 성공적으로 변환된 것을 볼 수 있다.

     

    d. 클래스 객체 

    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def encode_person(o):
      if isinstance(o, Person):
        return o.__dict__
      else:
        raise TypeError(f'Object of type {o.__class__.__name__} is not JSON serializable')
    
    def decode_person(o):
      return Person(o['name'], o['age'])
    
    print("====== Python -> JSON ======")
    person = Person('Bob', 42)
    json_person = json.dumps(person, default=encode_person)
    print("json_person :",json_person)
    print("type :",type(json_person))
    
    print("====== JSON -> Python ======")
    new_person = json.loads(json_person, object_hook=decode_person)
    print("new_person :",new_person.__dict__)
    print("type :",type(new_person))
    ====== Python -> JSON ======
    json_person : {"name": "Bob", "age": 42}
    type : <class 'str'>
    ====== JSON -> Python ======
    new_person : {'name': 'Bob', 'age': 42}
    type : <class '__main__.Person'>

    object_hook 키워드 인자를 활용하여 필요한 클래스의 새 객체를 생성하고 실제 데이터를 채우는 역할을 하는 함수를 가르킨다.

     

    decode_person의 함수는 Person을 생성하기위해 두개의 인자가 필요하므로 각각의 key와 value를 활용하도록 하였고,

    물론 key는 딕셔너리의 생성자 매개변수의 이름과 같아야 찾을 수 있다.
    decode_person 함수는 딕셔너리 o에서 o['name']과 o['age']로 값을 찾기 때문에,

    JSON 데이터의 키 이름이 Person 생성자의 매개변수 이름과 직접적으로 일치하거나,

    적어도 decode_person 함수가 해당 키를 정확히 찾아낼 수 있도록 매핑되어야 한다. 그렇지 않으면 KeyError가 발생할 수 있다.

     

    object_hook 함수는 json.loads()가 JSON 데이터 내에서 JSON 객체({}),

    즉 파이썬의 딕셔너리에 해당하는 부분을 파싱했을 때만 호출된다.

    리스트([]), 문자열(""), 숫자(123) 등 다른 JSON 타입에는 호출되지 않는다. 

    object_hook의 목적이 딕셔너리 형태의 데이터를 사용자 정의 객체로 변환하는 것이기 때문이다.

     

    import json
    
    
    class Person:
      def __init__(self, name, age):
        self.name = name
        self.age = age
    
    class Encoder(json.JSONEncoder):
      def default(self, o):
        if isinstance(o, Person):
          return o.__dict__
        else:
          return super().default(self, o)
    
    class Decoder(json.JSONDecoder):
      def __init__(self):
        json.JSONDecoder.__init__(self, object_hook=self.decode_person)
      
      def decode_person(self, o):
        return Person(**o)
    
    print("====== Python -> JSON ======")
    person = Person('Bob', 42)
    json_person = json.dumps(person, cls=Encoder)
    print("json_person :",json_person)
    print("type :",type(json_person))
    
    print("====== JSON -> Python ======")
    new_person = json.loads(json_person, cls=Decoder)
    print("new_person :",new_person.__dict__)
    print("type :",type(new_person))

    위와 같은 결과가 나온다.

    JSONEncoder 와 JSONDecoder를 상속받아 재정의하는 방식은 생각보다 복잡하다.

    JSONDecoder의 경우 생성자의 재정의도 필요하고, 해당 함수의 표현도 낮설다.

     

     

    Copyright 2024. GRAVITY all rights reserved