如何使一个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
如何使一个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
当前回答
如果你不介意为它安装一个包,你可以使用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,因为我遇到了与您相同的问题。
其他回答
你知道预期产量是多少吗?例如,这个可以吗?
>>> 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"}'
然后你把这个类作为cls kwarg传递给json.dumps()方法:
json.dumps(cls=MyEncoder)
如果还想解码,则必须向JSONDecoder类提供一个自定义object_hook。例如:
>>> 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>
>>>
这对我来说很有效:
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):
...
and
log.debug(json.dumps(<my object>, default=JsonSerializable.dumper, indent=2))
解决这个问题有很多方法。'ObjDict' (pip install object)是另一个。重点是提供像javascript一样的对象,它也可以像字典一样最好地处理从JSON加载的数据,但还有其他功能也很有用。这为原始问题提供了另一种解决方案。
首先,我们需要使我们的对象符合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
你们为什么要把事情搞得这么复杂?这里有一个简单的例子:
#!/usr/bin/env python3
import json
from dataclasses import dataclass
@dataclass
class Person:
first: str
last: str
age: int
@property
def __json__(self):
return {
"name": f"{self.first} {self.last}",
"age": self.age
}
john = Person("John", "Doe", 42)
print(json.dumps(john, indent=4, default=lambda x: x.__json__))
这样你也可以序列化嵌套类,因为__json__返回一个python对象而不是字符串。不需要使用JSONEncoder,因为使用简单lambda的默认参数也可以很好地工作。
我使用@property代替了一个简单的函数,因为这样感觉更自然和现代。@dataclass也只是一个例子,它也适用于“普通”类。