首页 > 解决方案 > 序列化和反序列化来自用户定义类的对象

问题描述

假设我有这样的类层次结构:

class SerializableWidget(object):
# some code

class WidgetA(SerilizableWidget):
# some code

class WidgetB(SerilizableWidget):
# some code

我希望能够将WidgetAWidgetB(以及可能的其他小部件)的实例序列化为文本文件为json. 然后,我希望能够在不事先知道它们的具体类的情况下反序列化它们:

some_widget = deserielize_from_file(file_path) # pseudocode, doesn't have to be exactly a method like this

并且some_widget需要从 的精确子类构造SerilizableWidget。我该怎么做呢?我需要在层次结构的每个类中覆盖/实现哪些方法?

假设上述类的所有字段都是原始类型。我如何覆盖一些__to_json____from_json__方法,类似的东西?

标签: pythonjsonserializationjson-deserializationjsonserializer

解决方案


你可以用很多方法解决这个问题。一个例子是分别使用object_hookdefault参数来json.loadjson.dump

您所需要做的就是将类与对象的序列化版本一起存储,然后在加载时必须使用哪个类与哪个名称对应的映射。

下面的例子使用dispatcher类装饰器在序列化的时候存储类名和对象,反序列化的时候再查找。您所需要的只是_as_dict每个类上的一个方法来将数据转换为字典:

import json

@dispatcher
class Parent(object):
    def __init__(self, name):
        self.name = name

    def _as_dict(self):
        return {'name': self.name}


@dispatcher
class Child1(Parent):
    def __init__(self, name, n=0):
        super().__init__(name)
        self.n = n

    def _as_dict(self):
        d = super()._as_dict()
        d['n'] = self.n
        return d

@dispatcher
class Child2(Parent):
    def __init__(self, name, k='ok'):
        super().__init__(name)
        self.k = k

    def _as_dict(self):
        d = super()._as_dict()
        d['k'] = self.k
        return d

现在进行测试。首先让我们创建一个包含 3 个不同类型对象的列表。

>>> obj = [Parent('foo'), Child1('bar', 15), Child2('baz', 'works')]

序列化它将产生每个对象中具有类名的数据:

>>> s = json.dumps(obj, default=dispatcher.encoder_default)
>>> print(s)
[
  {"__class__": "Parent", "name": "foo"},
  {"__class__": "Child1", "name": "bar", "n": 15},
  {"__class__": "Child2", "name": "baz", "k": "works"}
]

并将其加载回来会生成正确的对象:

obj2 = json.loads(s, object_hook=dispatcher.decoder_hook)
print(obj2)
[
  <__main__.Parent object at 0x7fb6cd561cf8>, 
  <__main__.Child1 object at 0x7fb6cd561d68>,
  <__main__.Child2 object at 0x7fb6cd561e10>
]

最后,这里的实现dispatcher

class _Dispatcher:
    def __init__(self, classname_key='__class__'):
        self._key = classname_key
        self._classes = {} # to keep a reference to the classes used

    def __call__(self, class_): # decorate a class
        self._classes[class_.__name__] = class_
        return class_

    def decoder_hook(self, d):
        classname = d.pop(self._key, None)
        if classname:
            return self._classes[classname](**d)
        return d

    def encoder_default(self, obj):
        d = obj._as_dict()
        d[self._key] = type(obj).__name__
        return d
dispatcher = _Dispatcher()

推荐阅读