如何使一个Python类序列化?

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

尝试序列化为JSON:

>>> import json
>>> x = FileItem('/foo/bar')
>>> json.dumps(x)
TypeError: Object of type 'FileItem' is not JSON serializable

当前回答

解决这个问题有很多方法。'ObjDict' (pip install object)是另一个。重点是提供像javascript一样的对象,它也可以像字典一样最好地处理从JSON加载的数据,但还有其他功能也很有用。这为原始问题提供了另一种解决方案。

其他回答

当我试图将Peewee的模型存储到PostgreSQL JSONField时,我遇到了这个问题。

在苦苦挣扎了一段时间后,这是通解。

我的解决方案的关键是浏览Python的源代码,并意识到代码文档(这里描述的)已经解释了如何扩展现有的json。转储以支持其他数据类型。

假设你现在有一个模型,其中包含一些不能序列化为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)

键是上面的默认(self, obj)方法。对于每一个……你从Python收到的不是JSON序列化的投诉,只需添加代码来处理不可序列化的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的,但我认为:

它一般适用于其他orm (Django等) 如果你理解json。dump可以工作,这个解决方案一般也适用于Python(无ORM)

有任何问题,请在评论区留言。谢谢!

另一种选择是将JSON转储打包到它自己的类中:

import json

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

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

或者,更好的是,从JsonSerializable类继承FileItem类:

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"}'

这是我的3美分… 这演示了一个树状python对象的显式json序列化。 注意:如果你真的想要这样的代码,你可以使用twisted 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)

首先,我们需要使我们的对象符合JSON,这样我们就可以使用标准JSON模块转储它。我是这样做的:

def serialize(o):
    if isinstance(o, dict):
        return {k:serialize(v) for k,v in o.items()}
    if isinstance(o, list):
        return [serialize(e) for e in o]
    if isinstance(o, bytes):
        return o.decode("utf-8")
    return o

如果你不介意为它安装一个包,你可以使用json-tricks:

pip install json-tricks

之后,你只需要从json_tricks导入dump(s)而不是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,因为我遇到了与您相同的问题。