如何使类JSON可序列化

如何使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
1257170 次浏览

你对预期的产出有什么想法吗?比如,这个可以吗?

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

在这种情况下,您只能调用json.dumps(f.__dict__)

如果您想要更多自定义输出,那么您将不得不子类#0并实现您自己的自定义序列化。

一个简单的例子,见下文。

>>> from json import JSONEncoder>>> class MyEncoder(JSONEncoder):def default(self, o):return o.__dict__
>>> MyEncoder().encode(f)'{"fname": "/foo/bar"}'

然后将这个类作为cls kwarg传递给#0方法:

json.dumps(cls=MyEncoder)

如果您还想解码,那么您必须为#1类提供自定义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>>>>

对于更复杂的类,您可以考虑工具json泡菜

JSON是一个Python库,用于将复杂的Python对象序列化和反序列化到JSON。

用于将Python编码为JSON的标准Python库,例如stdlib的json、simplejson和demjson,只能处理具有直接JSON等效的Python原语(例如dicts、列表、字符串、int等)。

(链接到PyPi上的json Pickle)

另一种选择是将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"}'

这是一个简单功能的简单解决方案:

.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 = 35me.dog = Object()me.dog.name = "Apollo"
print(me.toJSON())

将输出:

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

这是我的3美分…
这演示了树状python对象的显式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.pathd['isFile'] = o.isfile()d['isDir'] = o.isdir()d['mtime'] = int(o.getModificationTime())d['size'] = o.getsize() if o.isfile() else 0if o.isdir(): d['children'] = o.children()return d
folder = os.path.abspath('.')json.dump(File(folder), sys.stdout, default=_default)

这是一个小库,它将一个对象及其所有子对象序列化为JSON并将其解析回来:

https://github.com/tobiasholler/PyJSONSerialization/

对我来说,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 = idself.value = value
>>> data = DataModel(5, "foo")>>> dumper(data)'{"__type__": "DataModel", "id": 5, "value": "foo"}'

我喜欢Onur的回答,但会扩展为包含一个可选的toJSON()方法,用于对象序列化自己:

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

我想出了自己的解决方案。使用此方法,传递任何文档(字典列表对象名称等)进行序列化。

def getSerializable(doc):# check if it's a listif isinstance(doc, list):for i, val in enumerate(doc):doc[i] = getSerializable(doc[i])return doc
# check if it's a dictif isinstance(doc, dict):for key in doc.keys():doc[key] = getSerializable(doc[key])return doc
# Process ObjectIdif isinstance(doc, ObjectId):doc = str(doc)return doc
# Use any other custom serializting stuff here...
# For the rest of stuffreturn doc
import simplejson
class User(object):def __init__(self, name, mail):self.name = nameself.mail = mail
def _asdict(self):return self.__dict__
print(simplejson.dumps(User('alice', 'alice@mail.com')))

如果使用标准json,则需要定义default函数

import jsondef default(o):return o._asdict()
print(json.dumps(User('alice', 'alice@mail.com'), default=default))

大多数答案涉及将调用更改为json.dumps(),这并不总是可能或可取的(例如,它可能发生在框架组件中)。

如果您希望能够按原样调用json.dumps,那么一个简单的解决方案是从字典继承的:

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

如果你的类只是基本的数据表示,这是有效的,对于更棘手的事情,你总是可以显式设置键。

import json
class Foo(object):def __init__(self):self.bar = 'baz'self._qux = 'flub'
def somemethod(self):pass
def default(instance):return {k: vfor 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)

前几天我遇到了这个问题,并为Python对象实现了一个更通用的编码器版本,可以处理嵌套对象继承字段

import jsonimport 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"}

json在它可以打印的对象方面受到限制,jsonpickle(你可能需要pip install jsonpickle)在它不能缩进文本方面受到限制。如果你想检查一个你不能更改其类的对象的内容,我仍然找不到比以下更直接的方法:

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

注意:他们仍然不能打印对象方法。

只需将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().defaultJSONEncoder.default = _default

这将在导入时对json模块进行猴子补丁,所以JSONEncoder.default()自动检查特殊to_json()方法并使用它来编码对象(如果找到)。

就像奥努尔说的,但这次您不必更新项目中的每个json.dumps()

有很多方法可以解决这个问题。'ObjDlict'(pip安装对象)是另一个。强调提供类似javascript的对象,这些对象也可以像字典一样最好地处理从JSON加载的数据,但还有其他功能也很有用。这为原始问题提供了另一种替代解决方案。

这个类可以做到这一点,它将对象转换为标准json。

import json

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

用法:

Serializer.serialize(my_object)

python2.7python3中工作。

如果你不介意为它安装一个包,你可以使用json技巧

pip install json-tricks

之后,你只需要从json_tricks导入dump(s)而不是json,它通常会工作:

from json_tricks import dumpsjson_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 loadsjson_str = loads(json_str)

这确实假设module_name.test_class.MyTestCls可以导入并且没有以不兼容的方式更改。你会得到一个实例,不是一些字典或其他东西,它应该是与您转储的相同的副本。

如果您想自定义某些东西如何序列化(反序列化),您可以向您的类添加特殊方法,如下所示:

class CustomEncodeCls:def __init__(self):self.relevant = 42self.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 implicitlyself.relevant = attrs['relevant']self.irrelevant = 12

例如,它仅序列化部分属性参数。

作为免费奖励,您可以获得numpy数组、日期和时间、有序映射的(去)序列化,以及在json中包含注释的能力。

免责声明:我创建了json_tricks,因为我和你有同样的问题。

我选择使用装饰器来解决datetime对象序列化问题。这是我的代码:

#myjson.py#Author: jmooremcc 7/16/2017
import jsonfrom datetime import datetime, date, time, timedelta"""This module uses decorators to serialize date objects using jsonThe filename is myjson.pyIn another module you simply add the following import statement:from myjson import json
json.dumps and json.dump will then correctly serialize datetime and dateobjects"""
def json_serial(obj):"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):serial = str(obj)return serialraise 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

通过导入上述模块,我的其他模块以正常方式(不指定default关键字)使用json来序列化包含日期时间对象的数据。datetime序列化程序代码会自动调用json.dumps和json.dump.

我最喜欢Los Koder的方法。当尝试序列化成员/方法不可序列化的更复杂对象时,我遇到了问题。这是我在更多对象上工作的实现:

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

jaraco给出了一个非常简洁的答案。我需要修复一些小问题,但这有效:

代码

# Your custom classclass MyCustom(object):def __json__(self):return {'a': self.a,'b': self.b,'__python__': 'mymodule.submodule:MyCustom.from_json',}
to_json = __json__  # supported by simplejson
@classmethoddef from_json(cls, json):obj = cls()obj.a = json['a']obj.b = json['b']return obj
# Dumping and loadingimport simplejson
obj = MyCustom()obj.a = 3obj.b = 4
json = simplejson.dumps(obj, for_json=True)
# Two-step loadingobj2_dict = simplejson.loads(json)obj2 = MyCustom.from_json(obj2_dict)
# Make sure we have the correct thingassert isinstance(obj2, MyCustom)assert obj2.__dict__ == obj.__dict__

请注意,我们需要两个步骤来加载。现在,__python__属性不使用

这有多普遍?

使用AlJohri的方法,我检查方法的流行度:

序列化(Python->JSON):

  • #0:266,595 on 2018-06-27
  • #0:96,307 on 2018-06-27
  • #0:8,504 on 2018-06-27
  • #0:6,937 on 2018-06-27

反序列化(JSON->Python):

  • #0:226,101 on 2018-06-27

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

经过一段时间的挣扎,这里有一个通用的解决方案。

我的解决方案的关键是通过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)
@staticmethoddef 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投诉,只需添加代码来处理不可序列化的JSON类型(例如Enumdatetime

例如,以下是我如何支持从Enum继承的类:

class TransactionType(Enum):CURRENT = 1STACKED = 2
def default(self, obj):if isinstance(obj, TransactionType):return obj.valuereturn 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(无ORM)

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

如果你能够安装一个包,我建议尝试迪尔,它对我的项目工作得很好。这个包的一个好处是它与pickle具有相同的接口,所以如果你已经在你的项目中使用了pickle,你可以简单地替换dill并查看脚本是否运行,而无需更改任何代码。所以这是一个非常便宜的尝试解决方案!

(完全反披露:我与dill项目没有任何关系,也从未为dill项目做出贡献。

安装包:

pip install dill

然后编辑您的代码以导入dill而不是pickle

# import pickleimport dill as pickle

运行您的脚本并查看它是否有效。(如果是这样,您可能希望清理您的代码,以便您不再隐藏pickle模块名称!)

关于dill可以和不能序列化的数据类型的一些细节,来自的项目页面

dill可以泡制以下标准类型:

无,类型,布尔,int,长,浮动,复杂,str,Unicode,元组,列表,字典,文件,缓冲区,内置,新旧样式类,新旧样式类的实例,set,Frozenset,数组,函数,异常

dill还可以泡制更多“异国情调”的标准类型:

具有产量、嵌套函数、lambdas、单元格、方法、无边界方法、模块、代码、方法包装器、独占代理、方法描述符, getset描述符,成员描述符包装描述符,xrange,切片,非填充,省略号,退出

dill还不能泡制这些标准类型:

帧,发生器,回溯

如果您使用的是Python3.5+,您可以使用#0。(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

这对我来说工作得很好:

class JsonSerializable(object):
def serialize(self):return json.dumps(self.__dict__)
def __repr__(self):return self.serialize()
@staticmethoddef 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))

我没有看到这里提到串行版本控制或反向编译,所以我将发布我已经使用了一段时间的解决方案。我可能还有很多东西要学习,特别是Java和JavaScript可能比我更成熟

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

要添加另一个选项:您可以使用attrs包和asdict方法。

class ObjectEncoder(JSONEncoder):def default(self, o):return attr.asdict(o)
json.dumps(objects, cls=ObjectEncoder)

并转换回

def from_json(o):if '_obj_name' in o:type_ = o['_obj_name']del o['_obj_name']return globals()[type_](**o)else:return o
data = JSONDecoder(object_hook=from_json).decode(data)

类看起来像这样

@attr.sclass Foo(object):x = attr.ib()_obj_name = attr.ib(init=False, default='Foo')

除了Onur的回答之外,您可能还需要像下面这样处理datetime类型。
(为了处理:'datetime.datetime'对象没有属性'字典'异常。)

def datetime_option(value):if isinstance(value, datetime.date):return value.timestamp()else:return value.__dict__

用法:

def toJSON(self):return json.dumps(self, default=datetime_option, sort_keys=True, indent=4)

首先,我们需要使我们的对象符合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 objelse:return obj.__repr__()

基于QuintenCabo回答

def sterilize(obj):"""Make an object more ameniable to dumping as json"""if type(obj) in (str, float, int, bool, type(None)):return objelif 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] = avalelif 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_retelse:if len(dict_ret) == 0:return list_retreturn (list_ret, dict_ret)

的差异是

  1. 适用于任何可迭代对象,而不仅仅是listtuple(它适用于NumPy数组等)
  2. 适用于动态类型(包含__dict__的类型)。
  3. 包括本机类型floatNone,因此它们不会转换为字符串。
  4. 具有__dict__和成员的类将主要是工作(如果__dict__和成员名称冲突,您将只获得一个-可能是成员)
  5. 具有列表和成员的类看起来像列表的元组和字典
  6. Python3(isinstance()调用可能是唯一需要改变的东西)
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_dictelse:return o
a = DObject()a.name = 'abdul wahid'b = DObject()b.name = a
print(json.dumps(b, cls=DObject))

Kyle Delaney的评论是正确的所以我尝试使用答案https://stackoverflow.com/a/15538391/1497139以及https://stackoverflow.com/a/10254820/1497139的改进版本

创建一个“JSONAble”混音。

因此,要使类JSON可序列化,请使用“JSONAble”作为超类并调用:

 instance.toJSON()

 instance.asJSON()

您还可以使用此处提供的其他方法扩展JSONAble类。

使用Family和Person示例的单元测试的测试示例结果如下:

代码类型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=nameself.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混合

 '''Created on 2020-09-03
@author: wf'''import json
class JSONAble(object):'''mixin to allow classes to be JSON serializable seehttps://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 vlistelse: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://github.com/WolfgangFahl/pyLoDStorage项目中,该项目可在https://pypi.org/project/pylodstorage/获得

正如在许多其他答案中提到的,您可以将函数传递给json.dumps以将不属于默认支持的类型之一的对象转换为支持的类型。令人惊讶的是,他们中没有人提到最简单的情况,即使用内置函数#1将对象转换为包含其所有属性的字典:

json.dumps(obj, default=vars)

请注意,这仅涵盖基本情况,如果您需要对某些类型进行更具体的序列化(例如,排除某些属性或没有__dict__属性的对象),您需要使用自定义函数或其他答案中描述的JSONEncoder

TLDR:复制粘贴下面的选项1或选项2

真正的答案:
让Pythonsjson模块与您的类一起工作

又名,解决:json.dumps({ "thing": YOUR_CLASS() })


说明:

  • 有一个可靠的解决方案
  • 不,没有python“官方”解决方案
    • 通过官方解决方案,我的意思是(截至2022年)无法向你的类添加方法(如JavaScript中的toJSON)和/或无法使用内置的json模块注册你的类。当执行类似json.dumps([1,2, your_obj])的东西时,python不会检查查找表或对象方法。
    • 我不知道为什么其他答案解释不了这个
    • 最接近的官方方法可能是andyhasit的回答,它是从字典继承。然而,从字典继承对于许多自定义类来说并不是很好,比如AdvancedDateTime或pytorch张量。
  • 理想的解决方法是这样的:
    • Mutatejson.dumps(影响无处不在,甚至是导入json的pip模块)
    • def __json__(self)方法添加到您的类


选项1:让一个模块进行修补


pip install json-fix
(扩展+打包版约翰的回答,谢谢@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_CLASSimport json
json.dumps([1,2, YOUR_CLASS()], indent=0)# '[\n1,\n2,\n"a built-in object that is naturally json-able"\n]'

要使json.dumps适用于Numpy数组、Pandas DataFrames和其他第三方对象,请参阅该模块(只有大约2行代码,但需要解释)。




它是如何工作的?嗯…

选项2:自己json.dumps补丁


说明:这种方法被简化了,它在已知的edgecase上失败了(例如:如果您的自定义类继承自dict或其他内置类),并且它错过了控制外部类(numpy数组,datetime,dataframes,张量等)的json行为。

some_file_thats_imported_before_your_class_definitions.py

# Step: 1# create the patchfrom json import JSONEncoderdef wrapped_default(self, obj):return getattr(obj.__class__, "__json__", wrapped_default.default)(obj)wrapped_default.default = JSONEncoder().default   
# apply the patchJSONEncoder.original_default = JSONEncoder.defaultJSONEncoder.default = wrapped_default

your_class_definition.py

# Step 2class 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"

_

所有其他答案似乎都是“序列化自定义对象的最佳实践/方法”

其中,已经涵盖了在文档里(搜索“复杂”以获得编码复数的示例)

为了在这场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继承
  • 将每个对象属性映射到底层dict
class FileItem(dict):def __init__(self, fname):self['fname'] = fname
#fname propertyfname: str = property()@fname.getterdef fname(self):return self['fname']
@fname.setterdef fname(self, value: str):self['fname'] = value
#Repeat for other properties

是的,如果你有很多属性,这有点冗长,但它是JSONSerializable,它的行为就像一个对象,你可以把它给任何要json.dumps(obj)它的库。

你为什么要把它弄得这么复杂?这里有一个简单的例子:

#!/usr/bin/env python3
import jsonfrom dataclasses import dataclass
@dataclassclass Person:first: strlast: strage: int
@propertydef __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也只是一个例子,它也适用于“普通”类。

为了将另一个日志扔进10年前的火灾中,假设您使用的是Python 3.6+,我还会为此任务提供#0。这与数据类很好地配合,它实际上是3.7+以后的python内置模块。

dataclass-wizard库会将您的对象(及其所有属性递归地)转换为dict,并且通过fromdict使反向(反序列化)也非常简单。此外,这是PyPi链接:https://pypi.org/project/dataclass-wizard/

import dataclass_wizardimport dataclasses
@dataclasses.dataclassclass A:hello: stra_field: int
obj = A('world', 123)a_dict = dataclass_wizard.asdict(obj)# {'hello': 'world', 'aField': 123}

如果你想要一个字符串:

a_str = jsons.dumps(dataclass_wizard.asdict(obj))

或者,如果您的类从dataclass_wizard.JSONWizard扩展:

a_str = your_object.to_json()

最后,该库还支持Union类型的数据类,这基本上意味着dict可以反序列化为类C1C2的对象。例如:

from dataclasses import dataclass
from dataclass_wizard import JSONWizard
@dataclassclass Outer(JSONWizard):
class _(JSONWizard.Meta):tag_key = 'tag'auto_assign_tags = True
my_string: strinner: 'A | B'  # alternate syntax: `inner: typing.Union['A', 'B']`
@dataclassclass A:my_field: int
@dataclassclass B:my_field: str

my_dict = {'myString': 'test', 'inner': {'tag': 'B', 'myField': 'test'}}obj = Outer.from_dict(my_dict)
# Trueassert repr(obj) == "Outer(my_string='test', inner=B(my_field='test'))"
obj.to_json()# {"myString": "test", "inner": {"myField": "test", "tag": "B"}}

无论谁想要在没有外部库的情况下使用基本转换,只需使用以下方式覆盖自定义类的__iter____str__函数。

class JSONCustomEncoder(json.JSONEncoder):def default(self, obj):return obj.__dict__

class Student:def __init__(self, name: str, slug: str):self.name = nameself.age = age
def __iter__(self):yield from {"name": self.name,"age": self.age,}.items()
def __str__(self):return json.dumps(self.__dict__, cls=JSONCustomEncoder, ensure_ascii=False)

使用该对象,通过包装在一个字典(),使数据保持保留。

s = Student("aman", 24)dict(s)