클래스 인스턴스를 JSON으로 직렬화


186

클래스 인스턴스의 JSON 문자열 표현을 만들려고하는데 어려움이 있습니다. 클래스가 다음과 같이 구축되었다고 가정 해 봅시다.

class testclass:
    value1 = "a"
    value2 = "b"

json.dumps를 다음과 같이 호출합니다.

t = testclass()
json.dumps(t)

테스트 클래스가 JSON 직렬화 가능하지 않다고 말하고 실패했습니다.

TypeError: <__main__.testclass object at 0x000000000227A400> is not JSON serializable

또한 pickle 모듈을 사용해 보았습니다.

t = testclass()
print(pickle.dumps(t, pickle.HIGHEST_PROTOCOL))

또한 클래스 인스턴스 정보는 제공하지만 클래스 인스턴스의 직렬화 된 내용은 제공하지 않습니다.

b'\x80\x03c__main__\ntestclass\nq\x00)\x81q\x01}q\x02b.'

내가 뭘 잘못하고 있죠?



30
한 줄을 사용하여 s = json.dumps(obj, default=lambda x: x.__dict__)직렬화 객체의 인스턴스 변수에, ( self.value1, self.value2, ...). 가장 간단하고 가장 직접적인 방법입니다. 중첩 된 객체 구조를 직렬화합니다. default기능은 주어진 객체가 직접 직렬화하지 않을 때 호출된다. 아래에서 내 답변을 볼 수도 있습니다. 인기있는 답변이 불필요하게 복잡하다는 것을 알았습니다. 아마도 오래 전에 사실 일 것입니다.
codeman48

1
귀하는 testclass더없는 __init__()모든 인스턴스가 같은 두 개의 클래스 속성 (공유 할 수 있도록 방법 value1value2클래스 문에 정의 된). 클래스와 인스턴스의 차이점을 이해하십니까?
martineau

1
이것에 대한 파이썬 라이브러리가 github.com/jsonpickle/jsonpickle이 (답부터 주석하는 것은 스레드에서 너무 아래이고 도달 할 수 실 거예요.)
최고의 소원

답변:


238

기본 문제점은 JSON 인코더 json.dumps()가 기본적으로 모든 내장 유형의 제한된 오브젝트 유형 세트를 직렬화하는 방법 만 알고 있다는 것입니다. 여기에 나열하십시오 : https://docs.python.org/3.3/library/json.html#encoders-and-decoders

좋은 해결책 중 하나는 클래스를 상속 JSONEncoder하고 구현하는 것입니다.JSONEncoder.default() 함수 해당 함수가 클래스에 맞는 JSON을 생성하도록하는 것입니다.

간단한 해결책은 해당 인스턴스 json.dumps().__dict__멤버 를 호출 하는 것 입니다. 그것은 표준 파이썬 dict이며 클래스가 단순하면 JSON 직렬화 가능합니다.

class Foo(object):
    def __init__(self):
        self.x = 1
        self.y = 2

foo = Foo()
s = json.dumps(foo) # raises TypeError with "is not JSON serializable"

s = json.dumps(foo.__dict__) # s set to: {"x":1, "y":2}

위의 접근 방식은이 블로그 게시물에서 설명합니다.

    __dict__를 사용하여 임의의 Python 객체를 JSON으로 직렬화


3
나는 이것을 시도했다. json.dumps (t .__ dict__) 호출의 최종 결과는 {}입니다.
ferhan

6
클래스에 .__init__()메소드 함수 가 없기 때문에 클래스 인스턴스에 빈 사전이 있습니다. 다시 말해, {}예제 코드에 대한 올바른 결과입니다.
steveha

3
감사. 이것은 트릭을 수행합니다. 매개 변수없이 간단한 초기화 를 추가 하고 이제 json.dumps (t .__ dict__)를 호출하면 { "value2": "345", "value1": "123"} 형식으로 적절한 데이터를 반환합니다. 전에는 init 이 명시 적으로 언급되지 않았거나 놓친 멤버를 위해 사용자 정의 직렬 변환기가 필요한지 확실 하지 않았습니다. 감사합니다.
ferhan

3
이것은 하나의 클래스
에서만

2
@NwawelAIroume : 맞습니다. 당신은 예를 들어,이 목록에서 여러 객체를 포함하는 객체가있는 경우 오류가 여전히is not JSON serializable
gies0r

57

시험해 볼 수있는 한 가지 방법이 있습니다.

json.dumps()알 수없는 유형에 대해 사용자 정의 직렬 변환기 기능을 지정할 수 있는 선택적 매개 변수 기본값 을 사용할 수 있습니다.

def serialize(obj):
    """JSON serializer for objects not serializable by default json code"""

    if isinstance(obj, date):
        serial = obj.isoformat()
        return serial

    if isinstance(obj, time):
        serial = obj.isoformat()
        return serial

    return obj.__dict__

처음 두 경우는 날짜 및 시간 직렬화 obj.__dict__에 해당하며 다른 객체에 대해서는 반환됩니다.

최종 통화는 다음과 같습니다.

json.dumps(myObj, default=serialize)

컬렉션을 직렬화하고 전화를 원하지 않을 때 특히 좋습니다. __dict__ 모든 객체에 대해 명시 적으로 . 여기 자동으로 완료됩니다.

지금까지 당신의 생각을 기대하면서 저를 위해 아주 잘했습니다.


나는 얻는다 NameError: name 'serialize' is not defined. 팁이 있습니까?
Kyle Delaney

아주 좋아요 슬롯이있는 수업에만 해당 :try: dict = obj.__dict__ except AttributeError: dict = {s: getattr(obj, s) for s in obj.__slots__ if hasattr(obj, s)} return dict
fantastory

그러한 대중적인 언어에는 객체를 쏘는 하나의 라이너가 없다는 것이 놀랍습니다. 정적으로 입력되지 않았기 때문입니다.
TheRennen

49

함수 default에서 명명 된 매개 변수를 지정할 수 있습니다 json.dumps().

json.dumps(obj, default=lambda x: x.__dict__)

설명:

문서 양식 ( 2.7 , 3.6 ).

``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.

(Python 2.7 및 Python 3.x에서 작동)

참고 :이 경우 질문의 예와 같이 변수가 instance아닌 class변수가 필요합니다. (나는 asker class instance가 수업의 대상이 될 것이라고 가정하고 있다)

나는 @phihag의 답변 here 에서 이것을 처음 배웠습니다 . 그것이 작업을 수행하는 가장 간단하고 깨끗한 방법이라는 것을 알았습니다.


6
이것은 나를 위해 일했지만 datetime.date 멤버 때문에 약간 변경했습니다.default=lambda x: getattr(x, '__dict__', str(x))
Dakota Hawkins

@Dakota 좋은 해결 방법; datetime.dateC 구현이므로 __dict__속성 이 없습니다 . 균일 성을 위해 IMHO, datetime.date그것을 가지고 있어야합니다 ...
codeman48

22

나는 단지 :

data=json.dumps(myobject.__dict__)

이것은 정답이 아니며 복잡한 객체 클래스가 있으면 모든 것을 얻지는 못할 것입니다. 그러나 나는 이것을 간단한 객체 중 일부에 사용합니다.

정말 잘 작동하는 것은 OptionParser 모듈에서 얻는 "options"클래스입니다. 여기에 JSON 요청 자체가 있습니다.

  def executeJson(self, url, options):
        data=json.dumps(options.__dict__)
        if options.verbose:
            print data
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        return requests.post(url, data, headers=headers)

수업 내에서 이것을 사용하지 않으면 자기를 제거하고 싶을 수도 있습니다.
SpiRail

3
객체가 다른 객체로 구성되어 있지 않으면 정상적으로 작동합니다.
Haroldo_OK


5

JSON은 실제로 임의의 파이썬 객체를 직렬화하기위한 것이 아닙니다. dict객체 를 직렬화하는 데는 좋지만 pickle모듈은 실제로 일반적으로 사용해야합니다. 의 출력 pickle은 실제로 사람이 읽을 수는 없지만 잘 피해야합니다. JSON 사용을 고집한다면 jsonpickle흥미로운 하이브리드 방식 인 모듈을 확인할 수 있습니다 .

https://github.com/jsonpickle/jsonpickle


9
pickle에서 볼 수있는 주요 문제는 파이썬 특정 형식이고 JSON은 플랫폼 독립적 형식이라는 것입니다. JSON은 웹 애플리케이션 또는 일부 모바일 애플리케이션의 백엔드를 작성하는 경우 특히 유용합니다. jsonpickle을 지적 해 주셔서 감사합니다.
Haroldo_OK

@Haroldo_OK jsonpickle은 여전히 ​​인간이 읽을 수있는 JSON으로 내 보내지 않습니까?
Caelum

4

정교한 클래스가 아닌 직렬화를위한 두 가지 간단한 함수는 다음과 같습니다.

코드 조정없이 클래스에 새 멤버를 추가 할 수 있기 때문에 구성 유형에 사용합니다.

import json

class SimpleClass:
    def __init__(self, a=None, b=None, c=None):
        self.a = a
        self.b = b
        self.c = c

def serialize_json(instance=None, path=None):
    dt = {}
    dt.update(vars(instance))

    with open(path, "w") as file:
        json.dump(dt, file)

def deserialize_json(cls=None, path=None):
    def read_json(_path):
        with open(_path, "r") as file:
            return json.load(file)

    data = read_json(path)

    instance = object.__new__(cls)

    for key, value in data.items():
        setattr(instance, key, value)

    return instance

# Usage: Create class and serialize under Windows file system.
write_settings = SimpleClass(a=1, b=2, c=3)
serialize_json(write_settings, r"c:\temp\test.json")

# Read back and rehydrate.
read_settings = deserialize_json(SimpleClass, r"c:\temp\test.json")

# results are the same.
print(vars(write_settings))
print(vars(read_settings))

# output:
# {'c': 3, 'b': 2, 'a': 1}
# {'c': 3, 'b': 2, 'a': 1}

3

이 작업을 시작하는 방법에 대한 좋은 답변이 있습니다. 그러나 명심해야 할 것이 있습니다.

  • 인스턴스가 큰 데이터 구조 안에 중첩되어 있으면 어떻게됩니까?
  • 클래스 이름도 원하는 경우 어떻게합니까?
  • 인스턴스를 직렬화 해제하려면 어떻게해야합니까?
  • __slots__대신에 사용 하는 경우__dict__ ?
  • 직접하고 싶지 않다면?

json-tricks 는 꽤 오랫동안 이것을 할 수 있었던 라이브러리 (내가 만들고 다른 사람들이 기여한)입니다. 예를 들면 다음과 같습니다.

class MyTestCls:
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

cls_instance = MyTestCls(s='ub', dct={'7': 7})

json = dumps(cls_instance, indent=4)
instance = loads(json)

인스턴스를 다시 가져옵니다. 여기 json은 다음과 같습니다.

{
    "__instance_type__": [
        "json_tricks.test_class",
        "MyTestCls"
    ],
    "attributes": {
        "s": "ub",
        "dct": {
            "7": 7
        }
    }
}

당신이 당신의 자신의 솔루션을 만들고 싶다면, 당신은 json-tricks특별한 경우를 잊지 않도록 소스를 볼 수 있습니다 (예 :__slots__ ).

또한 numpy 배열, 날짜 시간, 복소수와 같은 다른 유형도 수행합니다. 또한 주석을 허용합니다.


3

Python3.x

내가 아는 가장 좋은 방법은 이것입니다.
이 코드는 set ()도 처리합니다.
이 접근 방식은 클래스 확장 (두 번째 예) 만 있으면됩니다.
파일로만 처리하고 있지만 취향에 따라 동작을 쉽게 수정할 수 있습니다.

그러나 이것은 CoDec입니다.

조금 더 노력하면 다른 방식으로 수업을 구성 할 수 있습니다. 기본 생성자를 인스턴스화한다고 가정 한 다음 클래스 dict를 업데이트합니다.

import json
import collections


class JsonClassSerializable(json.JSONEncoder):

    REGISTERED_CLASS = {}

    def register(ctype):
        JsonClassSerializable.REGISTERED_CLASS[ctype.__name__] = ctype

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in self.REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = self.REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


JsonClassSerializable.register(C)


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


JsonClassSerializable.register(B)


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()

JsonClassSerializable.register(A)

A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
print(b.b)
print(b.c.a)

편집하다

더 많은 연구 를 통해 메타 클래스를 사용하여 SUPERCLASS 레지스터 메소드 호출 이 필요없는 일반화 방법을 찾았습니다.

import json
import collections

REGISTERED_CLASS = {}

class MetaSerializable(type):

    def __call__(cls, *args, **kwargs):
        if cls.__name__ not in REGISTERED_CLASS:
            REGISTERED_CLASS[cls.__name__] = cls
        return super(MetaSerializable, cls).__call__(*args, **kwargs)


class JsonClassSerializable(json.JSONEncoder, metaclass=MetaSerializable):

    def default(self, obj):
        if isinstance(obj, collections.Set):
            return dict(_set_object=list(obj))
        if isinstance(obj, JsonClassSerializable):
            jclass = {}
            jclass["name"] = type(obj).__name__
            jclass["dict"] = obj.__dict__
            return dict(_class_object=jclass)
        else:
            return json.JSONEncoder.default(self, obj)

    def json_to_class(self, dct):
        if '_set_object' in dct:
            return set(dct['_set_object'])
        elif '_class_object' in dct:
            cclass = dct['_class_object']
            cclass_name = cclass["name"]
            if cclass_name not in REGISTERED_CLASS:
                raise RuntimeError(
                    "Class {} not registered in JSON Parser"
                    .format(cclass["name"])
                )
            instance = REGISTERED_CLASS[cclass_name]()
            instance.__dict__ = cclass["dict"]
            return instance
        return dct

    def encode_(self, file):
        with open(file, 'w') as outfile:
            json.dump(
                self.__dict__, outfile,
                cls=JsonClassSerializable,
                indent=4,
                sort_keys=True
            )

    def decode_(self, file):
        try:
            with open(file, 'r') as infile:
                self.__dict__ = json.load(
                    infile,
                    object_hook=self.json_to_class
                )
        except FileNotFoundError:
            print("Persistence load failed "
                  "'{}' do not exists".format(file)
                  )


class C(JsonClassSerializable):

    def __init__(self):
        self.mill = "s"


class B(JsonClassSerializable):

    def __init__(self):
        self.a = 1230
        self.c = C()


class A(JsonClassSerializable):

    def __init__(self):
        self.a = 1
        self.b = {1, 2}
        self.c = B()


A().encode_("test")
b = A()
b.decode_("test")
print(b.a)
# 1
print(b.b)
# {1, 2}
print(b.c.a)
# 1230
print(b.c.c.mill)
# s

2

나는 수용 된 대답에서 제안 된 상속 대신 다형성을 사용하는 것이 좋습니다. 그렇지 않으면 모든 객체의 인코딩을 사용자 정의하기 위해 큰 if else 문이 있어야합니다. 즉, JSON에 대한 일반적인 기본 인코더를 다음과 같이 생성하십시오.

def jsonDefEncoder(obj):
   if hasattr(obj, 'jsonEnc'):
      return obj.jsonEnc()
   else: #some default behavior
      return obj.__dict__

다음이 jsonEnc()당신이 직렬화 할 각 클래스의 기능을. 예 :

class A(object):
   def __init__(self,lengthInFeet):
      self.lengthInFeet=lengthInFeet
   def jsonEnc(self):
      return {'lengthInMeters': lengthInFeet * 0.3 } # each foot is 0.3 meter

그런 다음 전화 json.dumps(classInstance,default=jsonDefEncoder)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.