利用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 相关文章推荐
SublimeText 2编译python出错的解决方法(The system cannot find the file specified)
Nov 27 Python
python实现的登陆Discuz!论坛通用代码分享
Jul 11 Python
Python命令行参数解析模块getopt使用实例
Apr 13 Python
Python使用正则表达式实现文本替换的方法
Apr 18 Python
python遍历序列enumerate函数浅析
Oct 17 Python
启动targetcli时遇到错误解决办法
Oct 26 Python
[原创]教女朋友学Python3(二)简单的输入输出及内置函数查看
Nov 30 Python
Python面向对象class类属性及子类用法分析
Feb 02 Python
讲解Python3中NumPy数组寻找特定元素下标的两种方法
Aug 04 Python
根据tensor的名字获取变量的值方式
Jan 04 Python
在django中使用apscheduler 执行计划任务的实现方法
Feb 11 Python
浅谈Python3多线程之间的执行顺序问题
May 02 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中PDO基础教程 入门级
2011/09/04 PHP
PHP自定session保存路径及删除、注销与写入的方法
2014/11/18 PHP
PHP文件上传类实例详解
2016/04/08 PHP
javascript当onmousedown、onmouseup、onclick同时应用于同一个标签节点Element
2010/01/05 Javascript
Javascript 网页黑白效果实现代码(兼容IE/FF等)
2010/04/23 Javascript
jQuery ul标签下拉菜单演示代码
2010/12/11 Javascript
JS字符串累加Array不一定比字符串累加快(根据电脑配置)
2012/05/14 Javascript
eval的两组性能测试数据
2012/08/17 Javascript
js自定义方法通过隐藏iframe实现文件下载
2013/02/21 Javascript
基于JavaScript实现继承机制之构造函数方法对象冒充的使用详解
2013/05/07 Javascript
使用jquery.validate自定义方法实现"手机号码或者固话至少填写一个"的逻辑验证
2014/09/01 Javascript
原生JavaScript实现Ajax的方法
2016/04/07 Javascript
js调用父框架函数与弹窗调用父页面函数的简单方法
2016/11/01 Javascript
基于jquery实现的鼠标悬停提示案例
2016/12/11 Javascript
浅谈angular4实际项目搭建总结
2017/12/01 Javascript
轻量级JS Cookie插件js-cookie的使用方法
2018/03/22 Javascript
jQuery实现form表单序列化转换为json对象功能示例
2018/05/23 jQuery
Vue-Router基础学习笔记(小结)
2018/10/15 Javascript
Javascript中parseInt的正确使用方式
2018/10/17 Javascript
Vue-Cli 3.0 中配置高德地图的两种方式
2019/06/19 Javascript
pycharm 使用心得(一)安装和首次使用
2014/06/05 Python
使用python将图片格式转换为ico格式的示例
2018/10/22 Python
python+selenium实现QQ邮箱自动发送功能
2019/01/23 Python
python dlib人脸识别代码实例
2019/04/04 Python
Python_查看sqlite3表结构,查询语句的示例代码
2019/07/17 Python
python GUI库图形界面开发之PyQt5表单布局控件QFormLayout详细使用方法与实例
2020/03/06 Python
德国香水、化妆品和护理产品网上商店:Parfumdreams
2018/09/26 全球购物
若干个Java基础面试题
2015/05/19 面试题
大学本科生的个人自我评价
2013/12/09 职场文书
大学生入党推荐书范文
2014/05/17 职场文书
优秀应届本科生求职信
2014/07/19 职场文书
大学生旷课检讨书1000字
2015/02/19 职场文书
2015年妇联工作总结范文
2015/04/22 职场文书
总经理聘用协议书
2015/09/21 职场文书
2016年度基层党建工作公开承诺书
2016/03/25 职场文书
2016年幼儿园教师师德承诺书
2016/03/25 职场文书