41

用Python实现Json序列化库

 4 years ago
source link: https://insights.thoughtworks.cn/用python实现json序列化库/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在Python的世界里,将一个对象以json格式进行序列化或反序列化一直是一个问题。Python标准库里面提供了json序列化的工具,我们可以简单的用 json.dumps 来将一个对象序列化。但是这种序列化仅支持python内置的基本类型。

yiiyMzu.png!mobile

Python

在Python的世界里,将一个对象以json格式进行序列化或反序列化一直是一个问题。Python标准库里面提供了json序列化的工具,我们可以简单的用 json.dumps 来将一个对象序列化。但是这种序列化仅支持python内置的基本类型,对于自定义的类,我们将得到 Object of type A is not JSON serializable 的错误。

有很多种方法可以用来支持这种序列化, 这里 有一个很长的关于这个问题的讨论。总结起来,基本上有两种还不错的思路:

  1. 利用标准库的接口:从python标准json库中的 JSONDecoder 继承,然后自定义实现一个 default 方法用来自定义序列化过程
  2. 利用第三方库实现:如 jsonpickle jsonweb json-tricks

利用标准库的接口的问题在于,我们需要对每一个自定义类都实现一个 JSONDecoder.default 接口,难以实现代码复用。

利用第三方库,对我们的代码倒是没有任何侵入性,特别是 jsonpickle ,由于它是基于 pickle 标准序列化库实现,可以实现像pickle一样序列化任何对象,一行代码都不需要修改。

但是我们观察这类第三方库的输出的时候,会发现所有的这些类库都会在输出的json中增加一个特殊的标明对象类型的属性。这是为什么呢?Python是一门动态类型的语言,我们无法在对象还没有开始构建的时候知道对象的某一属性的类型信息,为了对反序列化提供支持,看起来确实是不得不这么做。

有人可能觉得这也无可厚非,似乎不影响使用。但是在跨语言通信的时候,这就成为了一个比较麻烦的问题。比如我们有一个Python实现的API,客户端发送了一个json请求过来,我们想在统一的一个地方将json反序列化为我们Python代码的对象。由于客户端不知道服务器端的类型信息,json请求里面就没法加入这样的类型信息,这也就导致这样的类库在反序列化的时候遇到问题。

能不能有一个相对完美的实现呢?先看一下我们理想的json序列化库的需求:

  1. 我们希望能简单的序列化任意自定义对象,只添加一行代码,或者不加入任何代码
  2. 我们希望序列化的结果不加入任何非预期的属性
  3. 我们希望能按照指定的类型进行反序列化,能自动处理嵌套的自定义类,只需要自定义类提供非常简单的支持,或者不需要提供任何支持
  4. 我们希望反序列化的时候能很好的处理属性不存在的情况,以便在我们加入某一属性的时候,可以设置默认值,使得旧版本的序列化结果可以正确的反序列化出来

如果有一个json库能支持上面的四点,那就基本是比较好用的库了。下面我们来尝试实现一下这个类库。

对于我们想要实现的几个需求,我们可以建立下面这样的测试来表达我们所期望的库的API设计:

class A(JsonSerializable):

    def __init__(self, a, b):
        super().__init__()
        self.a = a
        self.b = b if b is not None else B(0)

    @property
    def id(self):
        return self.a

    def _deserialize_prop(self, name, deserialized):
        if name == 'b':
            self.b = B.deserialize(deserialized)
            return
        super()._deserialize_prop(name, deserialized)

class B(JsonSerializable):

    def __init__(self, b):
        super().__init__()
        self.b = b

class JsonSerializableTest(unittest.TestCase):

    def test_model_should_serialize_correctly(self):
        self.assertEqual(json.dumps({'a': 1, 'b': {'b': 2}}), A(1, B(2)).serialize())

    def test_model_should_deserialize_correctly(self):
        a = A.deserialize(json.dumps({'a': 1, 'b': {'b': 2}}))
        self.assertEqual(1, a.a)
        self.assertEqual(2, a.b.b)

    def test_model_should_deserialize_with_default_value_correctly(self):
        a = A.deserialize(json.dumps({'a': 1}))
        self.assertEqual(1, a.a)
        self.assertEqual(0, a.b.b)

这里我们希望通过继承的方式来添加支持,这将在反序列化的时候提供一个好处。因为有了它我们就可以直接使用 A.deserialize 方法来反序列化,而不需要提供任何其他的反序列化函数参数,比如这样 json.deserialize(serialized_str, A)

同时为了验证我们的框架不会将 @property 属性序列化或者反序列化,我们特意在类 A 中添加了这样一个属性。

由于在反序列化的时候,框架是无法知道某一个对象属性的类型信息,比如测试中的 A.b ,为了能正确的反序列化,我们需要提供一点简单的支持,这里我们在类 A 中覆盖实现了一个父类的方法 _deserialize_prop 对属性 b 的反序列化提供支持。

当我们要反序列化一个之前版本的序列化结果时,我们希望能正确的反序列化并使用我们提供的默认值作为最终的反序列化值。这在属性 A.b 的测试中得到了体现。

(上面的测试有很多边界的情况、支持的变量类型并没有覆盖,此测试只是作为示例使用。)

如果能有一个类可以让上面的测试通过,相信那个类就是我们所需要的类了。这样的类可以实现为如下:

def is_normal_prop(obj, key):
    is_prop = isinstance(getattr(type(obj), key, None), property)
    is_func_attr = callable(getattr(obj, key))
    is_private_attr = key.startswith('__')
    return not (is_func_attr or is_prop or is_private_attr)

def is_basic_type(value):
    return value is None or type(value) in [int, float, str, bool]

class JsonSerializable:

    def _serialize_prop(self, name):
        return getattr(self, name)

    def _as_dict(self):
        props = {}
        for key in dir(self):
            if not is_normal_prop(self, key):
                continue
            value = self._serialize_prop(key)
            if not (is_basic_type(value) or isinstance(value, JsonSerializable)):
                raise Exception('unknown value to serialize to dict: key={}, value={}'.format(key, value))
            props[key] = value if is_basic_type(value) else value._as_dict()
        return props

    def serialize(self):
        return json.dumps(self._as_dict(), ensure_ascii=False)

    def _deserialize_prop(self, name, deserialized):
        setattr(self, name, deserialized)

    @classmethod
    def deserialize(cls, json_encoded):
        if json_encoded is None:
            return None

        args = inspect.getfullargspec(cls)
        args_without_self = args.args[1:]
        obj = cls(*([None] * len(args_without_self)))

        data = json.loads(json_encoded, encoding='utf8') if type(json_encoded) is str else json_encoded
        for key in dir(obj):
            if not is_normal_prop(obj, key):
                continue
            if key in data:
                obj._deserialize_prop(key, data[key])
        return obj

在实现时,我们利用了Python的内省机制,这样就可以自动的识别对象的属性及运行时类型了。当然对于这个简单的类还有很多待支持的功能,使用上也有很多限制,比如:

  1. 当某一属性为自定义类的类型的时候,需要子类覆盖实现 _deserialize_prop 方法为反序列化过程提供支持
  2. 当某一属性为由自定义类构成的一个 list tuple dict 复杂对象时,需要子类覆盖实现 _deserialize_prop 方法为反序列化过程提供支持
  3. 简单属性必须为python内置的基础类型,比如如果某一属性的类型为 numpy.float64 ,序列化反序列化将不能正常工作

虽然有上述限制,但是这正好要求我们在做模型设计的时候保持克制,不要将某一个对象设计得过于复杂。比如如果有属性为 dict 类型,我们可以将这个 dict 抽象为另一个自定义类型,然后用类型嵌套的方式来实现。

到这里这个基类就差不多可以支撑我们日常的开发需要了。当然对于这个简单的实现还有可能有其他的需求或者问题,大家如有发现,欢迎留言交流。

更多精彩洞见,请关注微信公众号:ThoughtWorks洞见


Recommend

  • 137
    • www.cnblogs.com 7 years ago
    • Cache

    Json的序列化与反序列化 - JF-Dev

    404 页面不存在 404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 [email protected]

  • 158
    • tech.meituan.com 7 years ago
    • Cache

    MSON,让JSON序列化更快

    MSON,让JSON序列化更快2018年01月09日 作者: 秦喆 芝任 天洲 赵鹏 文章链接 6426字 13分...

  • 79
    • studygolang.com 6 years ago
    • Cache

    特殊字符的json序列化

    先来看一段 golang package main import ( "encoding/json" "fmt" ) func main() { data := map[string]string{ "str0": "Hello, world", "str1": "<", "str2"...

  • 72

  • 42
    • www.tuicool.com 5 years ago
    • Cache

    json序列化和反序列化

    序列化和反序列化 1、JSON的序列化 1.1序列化 struct、map、slice 对于json的序列化和反序列化,go的encoding/json 包提供了一些列的方法。 常用的比如 func Marshal(v interface{}) ([]by...

  • 36
    • www.tuicool.com 5 years ago
    • Cache

    Golang语言之Json序列化

    Json序列化 指,将具有key-value(键 -> 值)结构的数据类型,例如:go语言中的map, slice,struct...序列化成json格式的字符串的操作。json是一种主流的数据传输格式,灵活轻便... 需要导入一个包 "encoding/json"

  • 30

    编译自 Custom JSON Marshalling in Go 。 我们知道,通过tag,可以有条件地实现定制Go JSON序列化的方式,比如 json:",omitempty" , 当字段的值为空的...

  • 8
    • www.guofei.site 3 years ago
    • Cache

    【Python】pickle&json序列化

    【Python】pickle&json序列化 2017年10月06日 Author: Guofei 文章归类: Python语法 ,文章编号: 1211 版权声明:本文作者是郭飞。转载随意...

  • 6

    常用的标准库 序列化模块 import pickle 序列化和反序列化 把不能直接存储的数据变得可存储,这个过程叫做序列化。把文件中的数据拿出来,回复称原来的数据类型,这个过程叫做反序列化。 在...

  • 2

    要在C#中实现JSON序列化和反序列化,您可以使用.NET Framework或.NET Core提供的System.Text.Json库或Newtonsoft.Json库。以下是一个使用System.Text.Json库进行JSON序列化和反序列化的示例:1. 引入命名空间使用System.Text.Json库进行JSON序列化和...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK