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

Posted

技术标签:

【中文标题】序列化和反序列化来自用户定义类的对象【英文标题】:Serialize and deserialize objects from user-defined classes 【发布时间】:2019-01-29 05:38:14 【问题描述】:

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

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

【问题讨论】:

谁认为这太宽泛而低估了我?我怎样才能使它更具体?它没有生产力! 你看过像Marshmallow这样的库吗? 为什么我需要一个额外的库,我可以不直接调用 json.dumps() 和 json.load() 吗?我需要做的就是在我的类中实现正确的方法。我错过了什么? 您并不严格需要第三方库,但序列化比您想象的要复杂得多(因此存在 Marshmallow 和其他类似库)。我建议查看我提供的链接并做出自己的决定。例如,Marshmallow 希望您定义模式,而 Django REST 框架希望您构建序列化程序。为什么你认为这两个都让你做体力活? 您知道我将如何在上述设置中执行此操作吗?假设这些类的所有字段都是原始类型。谢谢 【参考方案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()

【讨论】:

谢谢,正是我需要的!夫妻跟进:1)需要调度员吗? 2)我可以用jsonpickle达到同样的效果吗,优缺点是什么? @BaronYugovich dispatcher 只是可能的方法之一。我喜欢使用它,因为它清楚地说明了添加了哪些类以及添加的原因 - 它限制了 json 文件可以做什么。你装饰你想要的类,没有人可以用我不打算“公开”的类手工制作一个 json 文件。我对jsonpickle 没有任何经验,我不在我的项目中使用它的原因是它“太神奇了”......我喜欢明确的东西,比如定义我想要序列化的属性以及我如何想要它们在 json 文件中。也就是说,jsonpickle 可能使用了与此示例类似的方法。 @user2880391 from some_module import dispatcher 如果你把它放在some_module.py

以上是关于序列化和反序列化来自用户定义类的对象的主要内容,如果未能解决你的问题,请参考以下文章

反序列化为啥要找公开方法

序列化和反序列化漏洞的简单理解

Serializable接口的作用;Externalizable自定义序列化和反序列化

java序列化和反序列化

序列化和反序列化

java 序列化和反序列化