利用Python实现Json序列化库的方法步骤


Posted in Python onSeptember 09, 2020

前言

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

利用Python实现Json序列化库的方法步骤
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抽象为另一个自定义类型,然后用类型嵌套的方式来实现。

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

总结

到此这篇关于利用Python实现Json序列化库的文章就介绍到这了,更多相关Python实现Json序列化库内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
python正则表达式去掉数字中的逗号(python正则匹配逗号)
Dec 25 Python
使用python加密自己的密码
Aug 04 Python
python各种语言间时间的转化实现代码
Mar 23 Python
浅谈python import引入不同路径下的模块
Jul 11 Python
Python解析命令行读取参数--argparse模块使用方法
Jan 23 Python
python 将列表中的字符串连接成一个长路径的方法
Oct 23 Python
python实现事件驱动
Nov 21 Python
PyCharm 2019.3发布增加了新功能一览
Dec 08 Python
VSCode基础使用与VSCode调试python程序入门的图文教程
Mar 30 Python
Pytorch 卷积中的 Input Shape用法
Jun 29 Python
Python 字典一个键对应多个值的方法
Sep 29 Python
Python编程中内置的NotImplemented类型的用法
Mar 23 Python
python之语音识别speech模块
Sep 09 #Python
python speech模块的使用方法
Sep 09 #Python
python计算auc的方法
Sep 09 #Python
详解anaconda离线安装pytorchGPU版
Sep 08 #Python
python如何将图片转换素描画
Sep 08 #Python
Python自动化之UnitTest框架实战记录
Sep 08 #Python
Python Opencv实现单目标检测的示例代码
Sep 08 #Python
You might like
php-accelerator网站加速PHP缓冲的方法
2008/07/30 PHP
php替换超长文本中的特殊字符的函数代码
2012/05/22 PHP
ECSHOP在PHP5.5及高版本上报错的解决方法
2015/08/31 PHP
PHP安装threads多线程扩展基础教程
2015/11/17 PHP
PHP命名空间简单用法示例
2018/12/28 PHP
PHP强制转化的形式整理
2020/05/22 PHP
JavaScript 函数replace深入了解
2013/03/14 Javascript
js多级树形弹出一个小窗口层(非常好用)实例代码
2013/03/19 Javascript
jQuery实现移动 和 渐变特效的点击事件
2015/02/26 Javascript
JS获取图片lowsrc属性的方法
2015/04/01 Javascript
jQuery处理图片加载失败的常用方法
2015/06/08 Javascript
无刷新上传文件并返回自定义值
2015/06/11 Javascript
Javascript实现的简单右键菜单类
2015/09/23 Javascript
js实现纯前端的图片预览
2016/04/27 Javascript
JavaScript中对JSON对象的基本操作示例
2016/05/21 Javascript
JavaScript自学笔记(必看篇)
2016/06/23 Javascript
JS多物体实现缓冲运动效果示例
2016/12/20 Javascript
一个炫酷的Bootstrap导航菜单
2016/12/28 Javascript
js实现弹窗暗层效果
2017/01/16 Javascript
js中document.write和document.writeln的区别
2018/03/11 Javascript
Vue中nprogress页面加载进度条的方法实现
2020/11/13 Javascript
Python内置函数delattr的具体用法
2017/11/23 Python
python 判断矩阵中每行非零个数的方法
2019/01/26 Python
如何在Django中添加没有微秒的 DateTimeField 属性详解
2019/01/30 Python
Python实现直方图均衡基本原理解析
2019/08/08 Python
python web框架中实现原生分页
2019/09/08 Python
python中os.remove()用法及注意事项
2021/01/31 Python
详解CSS3新增的背景属性
2019/12/25 HTML / CSS
Ajax和javascript的区别
2013/07/20 面试题
汽车专业求职信
2014/06/05 职场文书
市场营销专业求职信
2014/06/17 职场文书
民事答辩状格式范文
2015/05/21 职场文书
组织委员竞选稿
2015/11/21 职场文书
党校团干班培训心得体会
2016/01/06 职场文书
CSS3 制作的书本翻页特效
2021/04/13 HTML / CSS
Java 写一个简单的图书管理系统
2022/04/26 Java/Android