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

当前回答

这是我的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)

其他回答

我有了自己的解决办法。使用此方法,将任何文档(字典、列表、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

我们经常在日志文件中转储JSON格式的复杂字典。虽然大多数字段携带重要信息,但我们不太关心内置的类对象(例如子进程)。Popen对象)。由于存在这些不可序列化的对象,对json.dumps()的调用会失败。

为了解决这个问题,我构建了一个小函数来转储对象的字符串表示形式,而不是转储对象本身。如果您正在处理的数据结构嵌套太多,您可以指定嵌套的最大级别/深度。

from time import time

def safe_serialize(obj , max_depth = 2):

    max_level = max_depth

    def _safe_serialize(obj , current_level = 0):

        nonlocal max_level

        # If it is a list
        if isinstance(obj , list):

            if current_level >= max_level:
                return "[...]"

            result = list()
            for element in obj:
                result.append(_safe_serialize(element , current_level + 1))
            return result

        # If it is a dict
        elif isinstance(obj , dict):

            if current_level >= max_level:
                return "{...}"

            result = dict()
            for key , value in obj.items():
                result[f"{_safe_serialize(key , current_level + 1)}"] = _safe_serialize(value , current_level + 1)
            return result

        # If it is an object of builtin class
        elif hasattr(obj , "__dict__"):
            if hasattr(obj , "__repr__"):
                result = f"{obj.__repr__()}_{int(time())}"
            else:
                try:
                    result = f"{obj.__class__.__name__}_object_{int(time())}"
                except:
                    result = f"object_{int(time())}"
            return result

        # If it is anything else
        else:
            return obj

    return _safe_serialize(obj)

由于字典也可以有不可序列化的键,转储它们的类名或对象表示将导致所有键都具有相同的名称,这将抛出错误,因为所有键都需要有唯一的名称,这就是为什么当前时间Since epoch被int(time())附加到对象名称。

可以使用以下具有不同级别/深度的嵌套字典来测试该函数

d = {
    "a" : {
        "a1" : {
            "a11" : {
                "a111" : "some_value" ,
                "a112" : "some_value" ,
            } ,
            "a12" : {
                "a121" : "some_value" ,
                "a122" : "some_value" ,
            } ,
        } ,
        "a2" : {
            "a21" : {
                "a211" : "some_value" ,
                "a212" : "some_value" ,
            } ,
            "a22" : {
                "a221" : "some_value" ,
                "a222" : "some_value" ,
            } ,
        } ,
    } ,
    "b" : {
        "b1" : {
            "b11" : {
                "b111" : "some_value" ,
                "b112" : "some_value" ,
            } ,
            "b12" : {
                "b121" : "some_value" ,
                "b122" : "some_value" ,
            } ,
        } ,
        "b2" : {
            "b21" : {
                "b211" : "some_value" ,
                "b212" : "some_value" ,
            } ,
            "b22" : {
                "b221" : "some_value" ,
                "b222" : "some_value" ,
            } ,
        } ,
    } ,
    "c" : subprocess.Popen("ls -l".split() , stdout = subprocess.PIPE , stderr = subprocess.PIPE) ,
}

执行以下命令将会得到-

print("LEVEL 3")
print(json.dumps(safe_serialize(d , 3) , indent = 4))

print("\n\n\nLEVEL 2")
print(json.dumps(safe_serialize(d , 2) , indent = 4))

print("\n\n\nLEVEL 1")
print(json.dumps(safe_serialize(d , 1) , indent = 4))

结果:

LEVEL 3
{
    "a": {
        "a1": {
            "a11": "{...}",
            "a12": "{...}"
        },
        "a2": {
            "a21": "{...}",
            "a22": "{...}"
        }
    },
    "b": {
        "b1": {
            "b11": "{...}",
            "b12": "{...}"
        },
        "b2": {
            "b21": "{...}",
            "b22": "{...}"
        }
    },
    "c": "<Popen: returncode: None args: ['ls', '-l']>"
}



LEVEL 2
{
    "a": {
        "a1": "{...}",
        "a2": "{...}"
    },
    "b": {
        "b1": "{...}",
        "b2": "{...}"
    },
    "c": "<Popen: returncode: None args: ['ls', '-l']>"
}



LEVEL 1
{
    "a": "{...}",
    "b": "{...}",
    "c": "<Popen: returncode: None args: ['ls', '-l']>"
}

[注意]:仅在不关心内置类对象的序列化时使用此选项。

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

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

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

对于更复杂的类,您可以考虑使用jsonpickle工具:

jsonpickle is a Python library for serialization and deserialization of complex Python objects to and from JSON. The standard Python libraries for encoding Python into JSON, such as the stdlib’s json, simplejson, and demjson, can only handle Python primitives that have a direct JSON equivalent (e.g. dicts, lists, strings, ints, etc.). jsonpickle builds on top of these libraries and allows more complex data structures to be serialized to JSON. jsonpickle is highly configurable and extendable–allowing the user to choose the JSON backend and add additional backends.

(链接到PyPi上的jsonpickle)