如何使一个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

当前回答

当我试图将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在它可以打印的对象方面受到限制,而jsonpickle(你可能需要一个PIP安装jsonpickle)在它不能缩进文本方面受到限制。如果你想检查一个你不能改变类的对象的内容,我仍然找不到比:

 import json
 import 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().default
JSONEncoder.default = _default

这将会在导入json模块时monkey-patch,所以 JSONEncoder.default()自动检查特殊的to_json() 方法,并使用它对找到的对象进行编码。

就像Onur说的,但是这次你不需要更新项目中的每个json.dumps()。

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

真正的/完整的答案:让Pythons json模块与你的类一起工作

AKA,求解:json。dump ({"thing": YOUR_CLASS()})


解释:

Yes, a good reliable solution exists No, there is no python "official" solution By official solution, I mean there is no way (as of 2023) to add a method to your class (like toJSON in JavaScript) and/or no way to register your class with the built-in json module. When something like json.dumps([1,2, your_obj]) is executed, python doesn't check a lookup table or object method. I'm not sure why other answers don't explain this The closest official approach is probably andyhasit's answer which is to inherit from a dictionary. However, inheriting from a dictionary doesn't work very well for many custom classes like AdvancedDateTime, or pytorch tensors. The ideal workaround is this: Mutate json.dumps (affects everywhere, even pip modules that import json) Add def __json__(self) method to your class



选项1:让一个模块来做补丁


PIP安装json-fix (扩展+包装版FancyJohn的回答,谢谢@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]'

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




它是如何工作的?嗯…

选项2:补丁json。把你自己


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

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"

_

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

在这里的文档中已经介绍过了(搜索“complex”可以找到编码复数的例子)

加拉科给出了一个非常简洁的答案。我需要修复一些小的东西,但这是有效的:

Code

# 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 on 2018-06-27 toJSON: 96,307 on 2018-06-27 __json__: 8504 on 2018-06-27 For_json: 6937 on 2018-06-27

反序列化(JSON -> Python):

From_json: 226,101 on 2018-06-27

你知道预期产量是多少吗?例如,这个可以吗?

>>> 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>
>>>