developer tip

클래스 JSON을 직렬화 가능하게 만드는 방법

optionbox 2020. 9. 30. 10:24
반응형

클래스 JSON을 직렬화 가능하게 만드는 방법


파이썬 클래스를 직렬화하는 방법은 무엇입니까?

간단한 수업 :

class FileItem:
    def __init__(self, fname):
        self.fname = fname

다음과 같은 결과를 얻으려면 어떻게해야합니까?

json.dumps()

오류 없음 ( FileItem instance at ... is not JSON serializable)


예상되는 출력에 대한 아이디어가 있습니까? 예를 들어 이것이 할 것인가?

>>> f  = FileItem("/foo/bar")
>>> magic(f)
'{"fname": "/foo/bar"}'

이 경우 json.dumps(f.__dict__).

더 많은 사용자 정의 출력을 원하면 JSONEncoder사용자 정의 직렬화 를 하위 클래스 화 하고 구현해야합니다.

간단한 예는 아래를 참조하십시오.

>>> from json import JSONEncoder
>>> class MyEncoder(JSONEncoder):
        def default(self, o):
            return o.__dict__    

>>> MyEncoder().encode(f)
'{"fname": "/foo/bar"}'

그런 다음이 클래스를 kwarg json.dumps()메소드에 cls전달합니다.

json.dumps(cls=MyEncoder)

디코딩도 원한다면 클래스에 커스텀 object_hook을 제공해야 JSONDecoder합니다. 예를 들어

>>> def from_json(json_object):
        if 'fname' in json_object:
            return FileItem(json_object['fname'])
>>> f = JSONDecoder(object_hook = from_json).decode('{"fname": "/foo/bar"}')
>>> f
<__main__.FileItem object at 0x9337fac>
>>> 

다음은 간단한 기능에 대한 간단한 솔루션입니다.

.toJSON() 방법

JSON 직렬화 가능 클래스 대신 직렬화 메소드를 구현하십시오.

import json

class Object:
    def toJSON(self):
        return json.dumps(self, default=lambda o: o.__dict__, 
            sort_keys=True, indent=4)

따라서 직렬화하기 위해 호출하면됩니다.

me = Object()
me.name = "Onur"
me.age = 35
me.dog = Object()
me.dog.name = "Apollo"

print(me.toJSON())

다음을 출력합니다.

{
    "age": 35,
    "dog": {
        "name": "Apollo"
    },
    "name": "Onur"
}

더 복잡한 클래스의 경우 jsonpickle 도구를 고려할 수 있습니다 .

jsonpickle은 복잡한 Python 객체와 JSON 간의 직렬화 및 역 직렬화를위한 Python 라이브러리입니다.

stdlib의 json, simplejson 및 demjson과 같이 Python을 JSON으로 인코딩하기위한 표준 Python 라이브러리는 직접 JSON에 해당하는 Python 기본 형식 (예 : dicts, 목록, 문자열, int 등) 만 처리 할 수 ​​있습니다. jsonpickle은 이러한 라이브러리 위에 빌드되며 더 복잡한 데이터 구조를 JSON으로 직렬화 할 수 있습니다. jsonpickle은 고도로 구성 가능하고 확장 가능하므로 사용자가 JSON 백엔드를 선택하고 추가 백엔드를 추가 할 수 있습니다.

(PyPi의 jsonpickle 링크)


대부분의 답변에는 json.dumps ()에 대한 호출 변경이 포함되며 , 이는 항상 가능하거나 바람직하지는 않습니다 (예 : 프레임 워크 구성 요소 내부에서 발생할 수 있음).

json.dumps (obj)를있는 그대로 호출 하려면 간단한 솔루션이 dict 에서 상속하는 것입니다 .

class FileItem(dict):
    def __init__(self, fname):
        dict.__init__(self, fname=fname)

f = FileItem('tasks.txt')
json.dumps(f)  #No need to change anything here

이것은 클래스가 기본 데이터 표현 일 경우 작동하며, 까다로운 작업을 위해 항상 명시 적으로 키를 설정할 수 있습니다.


또 다른 옵션은 JSON 덤프를 자체 클래스로 래핑하는 것입니다.

import json

class FileItem:
    def __init__(self, fname):
        self.fname = fname

    def __repr__(self):
        return json.dumps(self.__dict__)

또는 더 나은 방법은 클래스에서 FileItem 클래스를 서브 클래 싱하는 것 JsonSerializable입니다.

import json

class JsonSerializable(object):
    def toJson(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.toJson()


class FileItem(JsonSerializable):
    def __init__(self, fname):
        self.fname = fname

테스트 :

>>> f = FileItem('/foo/bar')
>>> f.toJson()
'{"fname": "/foo/bar"}'
>>> f
'{"fname": "/foo/bar"}'
>>> str(f) # string coercion
'{"fname": "/foo/bar"}'

나는 Onur의 대답을 좋아 하지만 toJSON()객체가 스스로 직렬화 하는 선택적 메소드 를 포함하도록 확장 합니다.

def dumper(obj):
    try:
        return obj.toJSON()
    except:
        return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)

요 전에이 문제를 발견하고 중첩 된 개체상속 된 필드를 처리 할 수있는 Python 개체 용 인코더의보다 일반적인 버전을 구현했습니다 .

import json
import inspect

class ObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj, "to_json"):
            return self.default(obj.to_json())
        elif hasattr(obj, "__dict__"):
            d = dict(
                (key, value)
                for key, value in inspect.getmembers(obj)
                if not key.startswith("__")
                and not inspect.isabstract(value)
                and not inspect.isbuiltin(value)
                and not inspect.isfunction(value)
                and not inspect.isgenerator(value)
                and not inspect.isgeneratorfunction(value)
                and not inspect.ismethod(value)
                and not inspect.ismethoddescriptor(value)
                and not inspect.isroutine(value)
            )
            return self.default(d)
        return obj

예:

class C(object):
    c = "NO"
    def to_json(self):
        return {"c": "YES"}

class B(object):
    b = "B"
    i = "I"
    def __init__(self, y):
        self.y = y

    def f(self):
        print "f"

class A(B):
    a = "A"
    def __init__(self):
        self.b = [{"ab": B("y")}]
        self.c = C()

print json.dumps(A(), cls=ObjectEncoder, indent=2, sort_keys=True)

결과:

{
  "a": "A", 
  "b": [
    {
      "ab": {
        "b": "B", 
        "i": "I", 
        "y": "y"
      }
    }
  ], 
  "c": {
    "c": "YES"
  }, 
  "i": "I"
}

to_json다음과 같이 클래스 메소드를 추가 하십시오.

def to_json(self):
  return self.message # or how you want it to be serialized

그리고이 코드 ( 이 답변에서 ) 를 모든 것의 맨 위에 추가하십시오 .

from json import JSONEncoder

def _default(self, obj):
    return getattr(obj.__class__, "to_json", _default.default)(obj)

_default.default = JSONEncoder().default
JSONEncoder.default = _default

가져올 때 json 모듈을 원숭이 패치하므로 JSONEncoder.default ()는 특수 "to_json ()"메서드를 자동으로 확인하고 발견 된 경우이를 사용하여 객체를 인코딩합니다.

Onur가 말한 것처럼 이번에는 json.dumps()프로젝트의 모든 항목을 업데이트 할 필요가 없습니다 .


Python3.5 이상을 사용하는 경우 jsons. 객체 (및 모든 속성을 재귀 적으로)를 dict로 변환합니다.

import jsons

a_dict = jsons.dump(your_object)

또는 문자열을 원하는 경우 :

a_str = jsons.dumps(your_object)

또는 클래스가 구현 된 경우 jsons.JsonSerializable:

a_dict = your_object.json

import simplejson

class User(object):
    def __init__(self, name, mail):
        self.name = name
        self.mail = mail

    def _asdict(self):
        return self.__dict__

print(simplejson.dumps(User('alice', 'alice@mail.com')))

표준 json을 사용하는 경우 default함수 를 정의해야 합니다.

import json
def default(o):
    return o._asdict()

print(json.dumps(User('alice', 'alice@mail.com'), default=default))

이 클래스는 트릭을 수행 할 수 있으며 객체를 표준 json으로 변환합니다.

import json


class Serializer(object):
    @staticmethod
    def serialize(object):
        return json.dumps(object, default=lambda o: o.__dict__.values()[0])

용법:

Serializer.serialize(my_object)

에서 작업 python2.7하고 python3.


json는 인쇄 할 수있는 개체의 측면에서 제한되며 jsonpickle(필요할 수 있음 pip install jsonpickle) 텍스트를 들여 쓸 수 없다는 측면에서 제한됩니다. 클래스를 변경할 수없는 객체의 내용을 검사하고 싶다면 다음보다 더 직접적인 방법을 찾을 수 없습니다.

 import json
 import jsonpickle
 ...
 print  json.dumps(json.loads(jsonpickle.encode(object)), indent=2)

참고 : 여전히 개체 메서드를 인쇄 할 수 없습니다.


import json

class Foo(object):
    def __init__(self):
        self.bar = 'baz'
        self._qux = 'flub'

    def somemethod(self):
        pass

def default(instance):
    return {k: v
            for k, v in vars(instance).items()
            if not str(k).startswith('_')}

json_foo = json.dumps(Foo(), default=default)
assert '{"bar": "baz"}' == json_foo

print(json_foo)

jaraco 는 꽤 깔끔한 대답을했습니다. 몇 가지 사소한 문제를 해결해야했지만 작동합니다.

암호

# Your custom class
class MyCustom(object):
    def __json__(self):
        return {
            'a': self.a,
            'b': self.b,
            '__python__': 'mymodule.submodule:MyCustom.from_json',
        }

    to_json = __json__  # supported by simplejson

    @classmethod
    def from_json(cls, json):
        obj = cls()
        obj.a = json['a']
        obj.b = json['b']
        return obj

# Dumping and loading
import simplejson

obj = MyCustom()
obj.a = 3
obj.b = 4

json = simplejson.dumps(obj, for_json=True)

# Two-step loading
obj2_dict = simplejson.loads(json)
obj2 = MyCustom.from_json(obj2_dict)

# Make sure we have the correct thing
assert isinstance(obj2, MyCustom)
assert obj2.__dict__ == obj.__dict__

로드하려면 두 단계가 필요합니다. 지금은 __python__속성이 사용되지 않습니다.

이것은 얼마나 흔한가요?

AlJohri 방법을 사용하여 접근 방식의 인기를 확인합니다.

직렬화 (Python-> JSON) :

역 직렬화 (JSON-> Python) :


이것은 나를 위해 잘 작동했습니다.

class JsonSerializable(object):

    def serialize(self):
        return json.dumps(self.__dict__)

    def __repr__(self):
        return self.serialize()

    @staticmethod
    def dumper(obj):
        if "serialize" in dir(obj):
            return obj.serialize()

        return obj.__dict__

그리고

class FileItem(JsonSerializable):
    ...

log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))

jsonweb이 나에게 가장 적합한 솔루션 인 것 같습니다. 참조 http://www.jsonweb.info/en/latest/를

from jsonweb.encode import to_object, dumper

@to_object()
class DataModel(object):
  def __init__(self, id, value):
   self.id = id
   self.value = value

>>> data = DataModel(5, "foo")
>>> dumper(data)
'{"__type__": "DataModel", "id": 5, "value": "foo"}'

패키지 설치에 신경 쓰지 않는다면 json-tricks를 사용할 수 있습니다 .

pip install json-tricks

그 후 당신은 단지 가져와야 dump(s)에서 json_tricks대신 JSON의, 그리고 작업 보통거야 :

from json_tricks import dumps
json_str = dumps(cls_instance, indent=4)

줄 것이다

{
        "__instance_type__": [
                "module_name.test_class",
                "MyTestCls"
        ],
        "attributes": {
                "attr": "val",
                "dct_attr": {
                        "hello": 42
                }
        }
}

그리고 그게 기본입니다!


이것은 일반적으로 훌륭하게 작동합니다. 예를 들어에서 특별한 일이 발생 __new__하거나 더 많은 메타 클래스 마법이 진행되는 경우와 같은 몇 가지 예외가 있습니다 .

분명히 로딩도 작동합니다 (그렇지 않으면 요점).

from json_tricks import loads
json_str = loads(json_str)

이것은 module_name.test_class.MyTestCls가져올 수 있고 호환되지 않는 방식으로 변경되지 않았다고 가정합니다 . 딕셔너리 등이 아닌 인스턴스가 반환되며 덤프 한 것과 동일한 복사본이어야합니다.

어떤 것이 (비) 직렬화되는 방법을 사용자 정의하려면 다음과 같이 클래스에 특수 메서드를 추가 할 수 있습니다.

class CustomEncodeCls:
        def __init__(self):
                self.relevant = 42
                self.irrelevant = 37

        def __json_encode__(self):
                # should return primitive, serializable types like dict, list, int, string, float...
                return {'relevant': self.relevant}

        def __json_decode__(self, **attrs):
                # should initialize all properties; note that __init__ is not called implicitly
                self.relevant = attrs['relevant']
                self.irrelevant = 12

예를 들어 속성 ​​매개 변수의 일부만 직렬화합니다.

그리고 무료 보너스로, numpy 배열, 날짜 및 시간, 정렬 된 맵의 (비) 직렬화 및 json에 주석을 포함 할 수있는 기능을 제공합니다.

면책 조항 : 나는 당신과 같은 문제가 있었기 때문에 json_tricks를 만들었습니다 .


여기 내 3 센트가 있습니다.
이것은 나무와 같은 파이썬 객체에 대한 명시적인 json 직렬화를 보여줍니다.
참고 : 실제로 이와 같은 코드가 필요하다면 트위스트 된 FilePath 클래스를 사용할 수 있습니다 .

import json, sys, os

class File:
    def __init__(self, path):
        self.path = path

    def isdir(self):
        return os.path.isdir(self.path)

    def isfile(self):
        return os.path.isfile(self.path)

    def children(self):        
        return [File(os.path.join(self.path, f)) 
                for f in os.listdir(self.path)]

    def getsize(self):        
        return os.path.getsize(self.path)

    def getModificationTime(self):
        return os.path.getmtime(self.path)

def _default(o):
    d = {}
    d['path'] = o.path
    d['isFile'] = o.isfile()
    d['isDir'] = o.isdir()
    d['mtime'] = int(o.getModificationTime())
    d['size'] = o.getsize() if o.isfile() else 0
    if o.isdir(): d['children'] = o.children()
    return d

folder = os.path.abspath('.')
json.dump(File(folder), sys.stdout, default=_default)

Peewee의 모델을 PostgreSQL에 저장하려고 할 때이 문제가 발생했습니다 JSONField.

한동안 고생 한 후 일반적인 해결책은 다음과 같습니다.

내 솔루션의 핵심은 Python의 소스 코드를 살펴보고 코드 문서 ( 여기에 설명되어 있음 )가 이미 json.dumps다른 데이터 유형을 지원 하도록 기존 항목 확장하는 방법을 설명 하고 있음을 인식하는 것 입니다.

현재 JSON으로 직렬화 할 수없는 일부 필드가 포함 된 모델이 있고 JSON 필드가 포함 된 모델이 원래 다음과 같다고 가정합니다.

class SomeClass(Model):
    json_field = JSONField()

다음 JSONEncoder과 같이 사용자 정의를 정의 하십시오.

class CustomJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, SomeTypeUnsupportedByJsonDumps):
            return < whatever value you want >
        return json.JSONEncoder.default(self, obj)

    @staticmethod
    def json_dumper(obj):
        return json.dumps(obj, cls=CustomJsonEncoder)

그런 다음 JSONField아래와 같이 사용하십시오 .

class SomeClass(Model):
    json_field = JSONField(dumps=CustomJsonEncoder.json_dumper)

핵심은 default(self, obj)방법입니다. ... is not JSON serializablePython으로부터받은 모든 불만 사항에 대해 직렬화 불가능 -JSON 유형 (예 : Enum또는 datetime) 을 처리하는 코드를 추가하기 만하면됩니다.

예를 들어, 다음에서 상속하는 클래스를 지원하는 방법은 Enum다음 같습니다.

class TransactionType(Enum):
   CURRENT = 1
   STACKED = 2

   def default(self, obj):
       if isinstance(obj, TransactionType):
           return obj.value
       return json.JSONEncoder.default(self, obj)

마지막으로, 위와 같이 구현 된 코드를 사용하면 모든 Peewee 모델을 아래와 같이 JSON 직렬화 가능한 객체로 변환 할 수 있습니다.

peewee_model = WhateverPeeweeModel()
new_model = SomeClass()
new_model.json_field = model_to_dict(peewee_model)

위의 코드는 (다소) Peewee에만 해당되지만 다음과 같이 생각합니다.

  1. 일반적으로 다른 ORM (Django 등)에 적용 가능
  2. 또한 json.dumps작동 방식 을 이해했다면 이 솔루션은 일반적으로 Python (sans ORM)에서도 작동합니다.

질문이 있으시면 댓글 섹션에 게시하십시오. 감사!


이것은 모든 자식이있는 객체를 JSON으로 직렬화하고 다시 파싱하는 작은 라이브러리입니다.

https://github.com/Toubs/PyJSONSerialization/


나는 내 자신의 해결책을 생각해 냈습니다. 이 메서드를 사용 하여 직렬화 할 문서 ( dict , list , ObjectId 등)를 전달합니다 .

def getSerializable(doc):
    # check if it's a list
    if isinstance(doc, list):
        for i, val in enumerate(doc):
            doc[i] = getSerializable(doc[i])
        return doc

    # check if it's a dict
    if isinstance(doc, dict):
        for key in doc.keys():
            doc[key] = getSerializable(doc[key])
        return doc

    # Process ObjectId
    if isinstance(doc, ObjectId):
        doc = str(doc)
        return doc

    # Use any other custom serializting stuff here...

    # For the rest of stuff
    return doc

날짜 시간 객체 직렬화 문제를 해결하기 위해 데코레이터를 사용하기로 결정했습니다. 내 코드는 다음과 같습니다.

#myjson.py
#Author: jmooremcc 7/16/2017

import json
from datetime import datetime, date, time, timedelta
"""
This module uses decorators to serialize date objects using json
The filename is myjson.py
In another module you simply add the following import statement:
    from myjson import json

json.dumps and json.dump will then correctly serialize datetime and date 
objects
"""

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

    if isinstance(obj, (datetime, date)):
        serial = str(obj)
        return serial
    raise TypeError ("Type %s not serializable" % type(obj))


def FixDumps(fn):
    def hook(obj):
        return fn(obj, default=json_serial)

    return hook

def FixDump(fn):
    def hook(obj, fp):
        return fn(obj,fp, default=json_serial)

    return hook


json.dumps=FixDumps(json.dumps)
json.dump=FixDump(json.dump)


if __name__=="__main__":
    today=datetime.now()
    data={'atime':today, 'greet':'Hello'}
    str=json.dumps(data)
    print str

위의 모듈을 가져 오면 다른 모듈은 기본 키워드를 지정하지 않고 일반적인 방식으로 json을 사용하여 날짜 시간 개체를 포함하는 데이터를 직렬화합니다. datetime serializer 코드는 json.dumps 및 json.dump에 대해 자동으로 호출됩니다.


나는 Lost Koder의 방법을 가장 좋아했습니다. 멤버 / 메소드를 직렬화 할 수없는 더 복잡한 개체를 직렬화하려고 할 때 문제가 발생했습니다. 더 많은 개체에서 작동하는 구현은 다음과 같습니다.

class Serializer(object):
    @staticmethod
    def serialize(obj):
        def check(o):
            for k, v in o.__dict__.items():
                try:
                    _ = json.dumps(v)
                    o.__dict__[k] = v
                except TypeError:
                    o.__dict__[k] = str(v)
            return o
        return json.dumps(check(obj).__dict__, indent=2)

패키지를 설치할 수 있다면 내 프로젝트에서 잘 작동 하는 dill을 사용해 보는 것이 좋습니다 . 이 패키지의 좋은 점은이 패키지가와 동일한 인터페이스를 가지고 있다는 것입니다 pickle. 따라서 pickle프로젝트에서 이미 사용 dill하고있는 경우 코드를 변경하지 않고 간단히 대체 하여 스크립트가 실행되는지 확인할 수 있습니다. 따라서 시도하는 것은 매우 저렴한 솔루션입니다!

(완전한 공개 금지 : 저는 딜 프로젝트와 관련이 없으며 기여한 적이 없습니다.)

패키지 설치 :

pip install dill

그런 다음 코드를 수정하여 다음 dill대신 가져올 수 있습니다 pickle.

# import pickle
import dill as pickle

스크립트를 실행하고 작동하는지 확인하십시오. (그렇다면 더 이상 pickle모듈 이름을 섀도 잉하지 않도록 코드를 정리할 수 있습니다 !)

프로젝트 페이지dill 에서 직렬화 할 수 있거나 할 수없는 데이터 유형에 대한 몇 가지 세부 사항 :

dill 다음 표준 유형을 피클 할 수 있습니다.

none, type, bool, int, long, float, complex, str, unicode, tuple, list, dict, file, buffer, builtin, both old and new style classes, instances of old and new style classes, set, frozenset, array , 함수, 예외

dill 더 많은 '이국적인'표준 유형을 피클 할 수도 있습니다.

yield, 중첩 함수, 람다, 셀, 메서드, unboundmethod, 모듈, 코드, methodwrapper, dictproxy, methoddescriptor, getsetdescriptor, memberdescriptor, wrapperdescriptor, xrange, slice, notimplemented, ellipsis, quit가있는 함수

dill 아직 이러한 표준 유형을 피클 할 수 없습니다.

프레임, 생성기, 트레이스 백


여기에 직렬 버전 관리 또는 백컴 패트에 대한 언급이 없으므로 잠시 사용했던 솔루션을 게시하겠습니다. 저는 배울 것이 더 많을 것입니다. 특히 Java와 Javascript는 아마도 저보다 성숙 할 것입니다.

https://gist.github.com/andy-d/b7878d0044a4242c0498ed6d67fd50fe


이 문제에 대한 많은 접근 방식이 있습니다. 'ObjDict'(pip install objdict)는 다른 것입니다. JSON에서로드 된 데이터를 가장 잘 처리하기 위해 딕셔너리처럼 작동 할 수도있는 객체와 같은 자바 스크립트를 제공하는 데 중점을 두지 만 유용 할 수있는 다른 기능도 있습니다. 이것은 원래 문제에 대한 또 다른 대안 솔루션을 제공합니다.

참고 URL : https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable

반응형