利用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判断操作系统类型代码分享
Nov 22 Python
Python 数据结构之堆栈实例代码
Jan 22 Python
Python调用C# Com dll组件实战教程
Oct 12 Python
Python SQL查询并生成json文件操作示例
Aug 17 Python
深入了解Django View(视图系统)
Jul 23 Python
使用python动态生成波形曲线的实现
Dec 04 Python
keras处理欠拟合和过拟合的实例讲解
May 25 Python
用python进行视频剪辑
Nov 02 Python
使用Python通过oBIX协议访问Niagara数据的示例
Dec 04 Python
python读取pdf格式文档的实现代码
Apr 01 Python
用 Python 元类的特性实现 ORM 框架
May 19 Python
Python面向对象编程之类的概念
Nov 01 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
第4章 数据处理-php数组的处理-郑阿奇
2011/07/04 PHP
eAccelerator的安装与使用详解
2013/06/13 PHP
php代码书写习惯优化小结
2013/06/20 PHP
PHP队列用法实例
2014/11/05 PHP
PHP实现的迷你漂流瓶
2015/07/29 PHP
php 函数中静态变量使用的问题实例分析
2020/03/05 PHP
JS 用6N±1法求素数 实例教程
2009/10/20 Javascript
超漂亮的Bootstrap 富文本编辑器summernote
2016/04/05 Javascript
jQuery解决$符号命名冲突
2016/06/18 Javascript
Node.js常用工具之util模块
2017/03/09 Javascript
关于预加载InstantClick的问题解决方法
2017/09/12 Javascript
vue超时计算的组件实例代码
2018/07/09 Javascript
vue框架搭建之axios使用教程
2018/07/11 Javascript
微信小程序自定义tabBar组件开发详解
2020/09/24 Javascript
Vue实现表格批量审核功能实例代码
2019/05/28 Javascript
解决vue项目F5刷新mounted里的函数不执行问题
2019/11/05 Javascript
vue实现淘宝购物车功能
2020/04/20 Javascript
微信小程序淘宝首页双排图片布局排版代码(推荐)
2020/10/29 Javascript
python 制作自定义包并安装到系统目录的方法
2018/10/27 Python
python动态视频下载器的实现方法
2019/09/16 Python
Python concurrent.futures模块使用实例
2019/12/24 Python
python 爬取古诗文存入mysql数据库的方法
2020/01/08 Python
python实现单张图像拼接与批量图片拼接
2020/03/23 Python
使用 Python 读取电子表格中的数据实例详解
2020/04/17 Python
Python爬虫爬取微信朋友圈
2020/08/06 Python
Python 3.9的到来到底是意味着什么
2020/10/14 Python
python 爬取英雄联盟皮肤并下载的示例
2020/12/04 Python
Marks & Spencer爱尔兰:英国马莎百货
2016/04/20 全球购物
Levi’s美国官网:美国著名的牛仔裤品牌
2016/08/19 全球购物
将n个数按输入顺序的逆序排列,用函数实现
2012/11/14 面试题
生物科学专业个人求职信范文
2013/12/07 职场文书
《再别康桥》教学反思
2014/02/12 职场文书
孝敬父母的活动方案
2014/08/31 职场文书
工资证明格式模板
2015/06/12 职场文书
2016母亲节感恩话语
2015/12/09 职场文书
MySQL中一条SQL查询语句是如何执行的
2022/04/08 MySQL