如何使 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
import jsons
请参阅下面的答案 - 效果很好
.to_dict()
函数或可以在对象上调用的东西,然后再将它传递给尝试序列化它的模块。
json.dumps
,但所有答案,包括获得的赏金,都涉及创建自定义编码器,这完全避开了问题的重点。
这是一个简单功能的简单解决方案:
.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"
}
你对预期的输出有什么想法吗?例如,这会做吗?
>>> 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>
>>>
__dict__
并非在所有情况下都有效。如果在实例化对象后尚未设置属性,则 __dict__
可能未完全填充。在上面的示例中,您没问题,但是如果您还想编码类属性,则这些属性不会列在 __dict__
中,除非它们已在类的 __init__
调用中或通过其他方式进行了修改在对象被实例化之后。
from_json()
函数应该有一个 else: return json_object
语句,因此它也可以处理一般对象。
__slots__
,@KrisHardy __dict__
也不起作用。
JSONEncoder
来创建自定义协议,例如检查 __json_serializable__
方法是否存在并调用它以获取对象的 JSON 可序列化表示。这将与其他 Python 模式保持一致,例如 __getitem__
、__str__
、__eq__
和 __len__
。
__dict__
也不会递归工作,例如,如果您的对象的属性是另一个对象。
对于更复杂的类,您可以考虑使用工具 jsonpickle:
jsonpickle 是一个 Python 库,用于将复杂的 Python 对象与 JSON 进行序列化和反序列化。用于将 Python 编码为 JSON 的标准 Python 库,例如 stdlib 的 json、simplejson 和 demjson,只能处理具有直接 JSON 等价物的 Python 原语(例如,dicts、lists、strings、ints 等)。 jsonpickle 建立在这些库之上,并允许将更复杂的数据结构序列化为 JSON。 jsonpickle 是高度可配置和可扩展的——允许用户选择 JSON 后端并添加额外的后端。
jsonpickle
对象。此外,这无法解码包含熊猫数据帧的字典。
obj = jsonpickle.decode(file.read())
和 file.write(jsonpickle.encode(obj))
。
大多数答案涉及更改对 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
如果您的类只是基本数据表示,则此方法有效,对于更棘手的事情,您始终可以显式设置键。
dumps
不是一个好的解决方案。顺便说一句,在大多数情况下,您可能希望将 dict
继承与委托一起使用,这意味着您将在您的类中拥有一些 dict
类型属性,然后您将将此属性作为参数作为初始化传递,例如 {4 }。
正如许多其他答案中提到的,您可以将一个函数传递给 json.dumps
以将不是默认支持的类型之一的对象转换为支持的类型。令人惊讶的是,他们都没有提到最简单的情况,即使用内置函数 vars
将对象转换为包含其所有属性的 dict:
json.dumps(obj, default=vars)
请注意,这仅涵盖基本情况,如果您需要对某些类型(例如排除某些属性或没有 __dict__
属性的对象)进行更具体的序列化,则需要使用自定义函数或 JSONEncoder
在其他答案中。
default=vars
是什么意思,这是否意味着 vars
是默认序列化程序?如果不是:这并不能真正解决您无法影响 json.dumps
调用方式的情况。如果您只是将一个对象传递给一个库并且该库对该对象调用 json.dumps
,那么如果该库不以这种方式使用 dumps
,那么您实现 vars
并没有真正的帮助。从这个意义上说,它等同于自定义 JSONEncoder
。
json.dumps
的调用方式,则无法解决此问题。
vars() argument must have __dict__ attribute
只需将 to_json
方法添加到您的类中,如下所示:
def to_json(self):
return self.message # or how you want it to be serialized
并将这段代码 (来自 this answer) 添加到所有内容的顶部:
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()
方法,并在找到时使用它对对象进行编码。
Just like Onur said,但这次您不必更新项目中的每个 json.dumps()
。
TheObject.to_json = my_serializer
。
import json _fallback = json._default_encoder.default json._default_encoder.default = lambda obj: getattr(obj.__class__, "to_json", _fallback)(obj)
我喜欢 Onur's answer,但会扩展为包含一个可选的 toJSON()
方法,以便对象自行序列化:
def dumper(obj):
try:
return obj.toJSON()
except:
return obj.__dict__
print json.dumps(some_big_object, default=dumper, indent=2)
try-catch
可能会做类似 if 'toJSON' in obj.__attrs__():
的事情......以避免静默失败(如果 toJSON() 由于其他原因而失败,而不是它不存在)......可能导致的失败到数据损坏。
AttributeError
obj.toJSON()
中引发 AttributeError
会怎样?
另一种选择是将 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"}'
__json__encode__
/ __json_decode__
来更改它(披露:我做了最后一个)。
json.dumps(f)
将失败。这不是被问到的。
如果您使用的是 Python3.5+,则可以使用 jsons
。 (PyPi:https://pypi.org/project/jsons/)它将您的对象(及其所有属性递归)转换为字典。
import jsons
a_dict = jsons.dump(your_object)
或者如果你想要一个字符串:
a_str = jsons.dumps(your_object)
或者,如果您的班级实施了 jsons.JsonSerializable
:
a_dict = your_object.json
jsons
库与 dataclasses 混合使用。到目前为止,对我很好!
前几天我遇到了这个问题,并为 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"
}
return obj
,而是使用了 return super(ObjectEncoder, self).default(obj)
。参考HERE
真正的答案:让 Python 的 json 模块与你的类一起工作
又名,解决:json.dumps({ "thing": YOUR_CLASS() })
TLDR:复制粘贴下面的选项 1 或选项 2
解释:
是的,存在一个可靠的解决方案
不,没有 python“官方”解决方案 通过官方解决方案,我的意思是(截至 2022 年)无法向您的类添加方法(如 JavaScript 中的 toJSON)和/或无法将您的类注册到内置- 在 json 模块中。当执行 json.dumps([1,2, your_obj]) 之类的东西时,python 不会检查查找表或对象方法。我不确定为什么其他答案不能解释这一点最接近的官方方法可能是 andyhasit 的答案,即从字典继承。但是,对于许多自定义类(如 AdvancedDateTime 或 pytorch 张量)来说,从字典继承并不是很好。
通过官方解决方案,我的意思是(截至 2022 年)无法向您的类添加方法(如 JavaScript 中的 toJSON)和/或无法使用内置 json 模块注册您的类。当执行 json.dumps([1,2, your_obj]) 之类的东西时,python 不会检查查找表或对象方法。
我不确定为什么其他答案不能解释这一点
最接近的官方方法可能是 andyhasit 的答案,即从字典继承。但是,对于许多自定义类(如 AdvancedDateTime 或 pytorch 张量)来说,从字典继承并不是很好。
理想的解决方法是: Mutate json.dumps (影响任何地方,甚至是导入 json 的 pip 模块) 添加 def __json__(self) 方法到你的类
改变 json.dumps(影响任何地方,甚至是导入 json 的 pip 模块)
将 def __json__(self) 方法添加到您的类
选项 1:让模块进行修补
pip install json-fix
(Fancy John's answer 的扩展 + 打包版本,谢谢 @FancyJohn)
your_class_definition.py
import json_fix
class YOUR_CLASS:
def __json__(self):
# YOUR CUSTOM CODE HERE
# you probably just want to do:
# return self.__dict__
return "a built-in object that is naturally json-able"
而已。示例用法:
from your_class_definition import YOUR_CLASS
import json
json.dumps([1,2, YOUR_CLASS()], indent=0)
# '[\n1,\n2,\n"a built-in object that is naturally json-able"\n]'
它是如何工作的?请参阅选项 2 自行完成。
注意:
要使 json.dumps
适用于 Numpy 数组、Pandas DataFrames 和其他第 3 方对象,请参阅 the Module(仅约 2 行代码,但需要解释) .
选项 2:自己修补 json.dumps
注意:这种方法被简化了,并且错过了控制外部类(numpy 数组、日期时间、数据帧、张量等)的 json 行为。
some_file_thats_imported_before_your_class_definitions.py
# Step: 1
# create the patch
from json import JSONEncoder
def wrapped_default(self, obj):
return getattr(obj.__class__, "__json__", wrapped_default.default)(obj)
wrapped_default.default = JSONEncoder().default
# apply the patch
JSONEncoder.original_default = JSONEncoder.default
JSONEncoder.default = wrapped_default
your_class_definition.py
# Step 2
class YOUR_CLASS:
def __json__(self, **options):
# YOUR CUSTOM CODE HERE
# you probably just want to do:
# return self.__dict__
return "a built-in object that is natually json-able"
_
所有其他答案似乎都是“序列化自定义对象的最佳实践/方法”
其中,已涵盖 here in the docs(搜索“复杂”以获取编码复数的示例)
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.dumps(User('alice', 'alice@mail.com'), default=lambda x: x.__dict__)
删除 _asdict 函数来简化这一点
json
在可以打印的对象方面受到限制,而 jsonpickle
(您可能需要 pip install jsonpickle
)在不能缩进文本方面受到限制。如果您想检查无法更改其类的对象的内容,我仍然找不到比以下更直接的方法:
import json
import jsonpickle
...
print json.dumps(json.loads(jsonpickle.encode(object)), indent=2)
注意:他们仍然无法打印对象方法。
这是我的 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)
这个类可以解决问题,它将 object 转换为标准 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
工作。
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):
to_json: 266,595 在 2018-06-27
toJSON:2018 年 6 月 27 日 96,307
__json__:2018-06-27 上的 8,504
for_json:2018 年 6 月 27 日的 6,937
反序列化(JSON -> Python):
from_json: 226,101 于 2018 年 6 月 27 日
这对我来说效果很好:
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))
如果您不介意为其安装软件包,可以使用 json-tricks:
pip install json-tricks
之后,您只需从 json_tricks
而不是 json 导入 dump(s)
,它通常会起作用:
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,因为我遇到了和你一样的问题。
Kyle Delaney's comment is correct 所以我尝试使用答案 https://stackoverflow.com/a/15538391/1497139 以及 https://stackoverflow.com/a/10254820/1497139 的改进版本
创建一个“JSONAble”混合。
因此,要使类 JSON 可序列化,请使用“JSONAble”作为超类并调用:
instance.toJSON()
或者
instance.asJSON()
对于提供的两种方法。您还可以使用此处提供的其他方法扩展 JSONAble 类。
带有家庭和个人样本的单元测试的测试示例导致:
toJSON():
{
"members": {
"Flintstone,Fred": {
"firstName": "Fred",
"lastName": "Flintstone"
},
"Flintstone,Wilma": {
"firstName": "Wilma",
"lastName": "Flintstone"
}
},
"name": "The Flintstones"
}
asJSon():
{'name': 'The Flintstones', 'members': {'Flintstone,Fred': {'firstName': 'Fred', 'lastName': 'Flintstone'}, 'Flintstone,Wilma': {'firstName': 'Wilma', 'lastName': 'Flintstone'}}}
带有家庭和个人样本的单元测试
def testJsonAble(self):
family=Family("The Flintstones")
family.add(Person("Fred","Flintstone"))
family.add(Person("Wilma","Flintstone"))
json1=family.toJSON()
json2=family.asJSON()
print(json1)
print(json2)
class Family(JSONAble):
def __init__(self,name):
self.name=name
self.members={}
def add(self,person):
self.members[person.lastName+","+person.firstName]=person
class Person(JSONAble):
def __init__(self,firstName,lastName):
self.firstName=firstName;
self.lastName=lastName;
jsonable.py 定义 JSONAble mixin
'''
Created on 2020-09-03
@author: wf
'''
import json
class JSONAble(object):
'''
mixin to allow classes to be JSON serializable see
https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable
'''
def __init__(self):
'''
Constructor
'''
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__,
sort_keys=True, indent=4)
def getValue(self,v):
if (hasattr(v, "asJSON")):
return v.asJSON()
elif type(v) is dict:
return self.reprDict(v)
elif type(v) is list:
vlist=[]
for vitem in v:
vlist.append(self.getValue(vitem))
return vlist
else:
return v
def reprDict(self,srcDict):
'''
get my dict elements
'''
d = dict()
for a, v in srcDict.items():
d[a]=self.getValue(v)
return d
def asJSON(self):
'''
recursively return my dict elements
'''
return self.reprDict(self.__dict__)
您会发现这些方法现已集成在 https://pypi.org/project/pylodstorage/ 上的 https://github.com/WolfgangFahl/pyLoDStorage 项目中
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"}'
class DObject(json.JSONEncoder):
def delete_not_related_keys(self, _dict):
for key in ["skipkeys", "ensure_ascii", "check_circular", "allow_nan", "sort_keys", "indent"]:
try:
del _dict[key]
except:
continue
def default(self, o):
if hasattr(o, '__dict__'):
my_dict = o.__dict__.copy()
self.delete_not_related_keys(my_dict)
return my_dict
else:
return o
a = DObject()
a.name = 'abdul wahid'
b = DObject()
b.name = a
print(json.dumps(b, cls=DObject))
基于 Quinten Cabo 的 answer:
def sterilize(obj):
"""Make an object more ameniable to dumping as json
"""
if type(obj) in (str, float, int, bool, type(None)):
return obj
elif isinstance(obj, dict):
return {k: sterilize(v) for k, v in obj.items()}
list_ret = []
dict_ret = {}
for a in dir(obj):
if a == '__iter__' and callable(obj.__iter__):
list_ret.extend([sterilize(v) for v in obj])
elif a == '__dict__':
dict_ret.update({k: sterilize(v) for k, v in obj.__dict__.items() if k not in ['__module__', '__dict__', '__weakref__', '__doc__']})
elif a not in ['__doc__', '__module__']:
aval = getattr(obj, a)
if type(aval) in (str, float, int, bool, type(None)):
dict_ret[a] = aval
elif a != '__class__' and a != '__objclass__' and isinstance(aval, type):
dict_ret[a] = sterilize(aval)
if len(list_ret) == 0:
if len(dict_ret) == 0:
return repr(obj)
return dict_ret
else:
if len(dict_ret) == 0:
return list_ret
return (list_ret, dict_ret)
区别是
适用于任何可迭代对象,而不仅仅是列表和元组(它适用于 NumPy 数组等)适用于动态类型(包含 __dict__ 的类型)。包括本机类型 float 和 None,因此它们不会转换为字符串。具有 __dict__ 和成员的类大部分都可以工作(如果 __dict__ 和成员名称冲突,您只会得到一个 - 可能是成员)作为列表并具有成员的类看起来像列表的元组和字典 Python3(即实例() 调用可能是唯一需要改变的东西)
我最喜欢 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)
当我尝试将 Peewee 的模型存储到 PostgreSQL JSONField
中时遇到了这个问题。
经过一段时间的挣扎,这是一般的解决方案。
我的解决方案的关键是通过 Python 的源代码并意识到代码文档(描述 here)已经解释了如何扩展现有的 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)
方法。对于您从 Python 收到的每一个 ... is not JSON serializable
投诉,只需添加代码来处理 unserializable-to-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.dumps 的工作原理,该解决方案通常也适用于 Python(无 ORM)
有任何问题,请在评论区留言。谢谢!
首先,我们需要使我们的对象符合 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
此函数使用递归遍历字典的每个部分,然后调用非内置类型的类的 repr() 方法。
def sterilize(obj):
object_type = type(obj)
if isinstance(obj, dict):
return {k: sterilize(v) for k, v in obj.items()}
elif object_type in (list, tuple):
return [sterilize(v) for v in obj]
elif object_type in (str, int, bool, float):
return obj
else:
return obj.__repr__()
要在这个 11 年的火灾中再扔一个日志,我想要一个满足以下标准的解决方案:
允许仅使用 json.dumps(obj) 序列化类 FileItem 的实例
允许 FileItem 实例具有属性:fileItem.fname
允许将 FileItem 实例提供给任何将使用 json.dumps(obj) 对其进行序列化的库
不需要将任何其他字段传递给 json.dumps(如自定义序列化程序)
IE:
fileItem = FileItem('filename.ext')
assert json.dumps(fileItem) == '{"fname": "filename.ext"}'
assert fileItem.fname == 'filename.ext'
我的解决方案是:
让 obj 的类继承自 dict
将每个对象属性映射到底层字典
class FileItem(dict):
def __init__(self, fname):
self['fname'] = fname
#fname property
fname: str = property()
@fname.getter
def fname(self):
return self['fname']
@fname.setter
def fname(self, value: str):
self['fname'] = value
#Repeat for other properties
是的,如果您有很多属性,这有点冗长,但它是 JSONSerializable 并且它的行为就像一个对象,您可以将它提供给任何将要json.dumps(obj)
它的库。
为什么你们把事情搞得这么复杂?这是一个简单的例子:
#!/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 的 default
参数也可以正常工作。
我使用 @property
而不是一个简单的函数,因为这感觉更自然和现代。 @dataclass
也只是一个示例,它也适用于“普通”类。
__json__
属性,这有时会很麻烦。此外,dataclasses 提供了 asdict
,因此从技术上讲,您根本不需要 __json__
属性。
asdict
不适用于嵌套元素,对吧?
InitVar
(仅限初始化)字段,并在 __post_init__
构造函数中设置 name 字段。我认为这应该有望在这种情况下以差异格式表示 json。另外,我可能错了,但我相信 asdict
也适用于嵌套数据类。
我想出了自己的解决方案。使用此方法,传递任何文档(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
o.__dict___
。试试你自己的例子:class MyObject(): def __init__(self): self.prop = 1 j = json.dumps({ "foo": "bar", "baz": MyObject() }, default=lambda o: o.__dict__)
datetime.datetime
个实例。它引发以下错误:'datetime.datetime' object has no attribute '__dict__'
json.dumps(me)
没有调用Object
的toJSON
方法。