从用户定义的类序列化和反序列化对象

时间:2018-08-22 21:50:30

标签: python json serialization json-deserialization jsonserializer

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

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__方法,诸如此类?

1 个答案:

答案 0 :(得分:1)

您可以使用多种方法解决此问题。一个示例是将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()