在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 SerializableModelTest(unittest.TestCase):def test_model_serializable(self):class A(SerializableModel):def __init__(self, a, b):super().__init__()self.a = aself.b = b if b is not None else B(0)@propertydef id(self):return self.adef _deserialize_prop(self, name, deserialized):if name == 'b':self.b = B.deserialize(deserialized)returnsuper()._deserialize_prop(name, deserialized)class B(SerializableModel):def __init__(self, b):super().__init__()self.b = bself.assertEqual(json.dumps({'a': 1, 'b': {'b': 2}, 'long_attr': None}), A(1, B(2)).serialize())self.assertEqual(json.dumps({'a': 1, 'b': None}), A(1, None).serialize())self.assertEqual(A(1, B(2)), A.deserialize(json.dumps({'a': 1, 'b': {'b': 2}})))self.assertEqual(A(1, None), A.deserialize(json.dumps({'a': 1, 'b': None})))self.assertEqual(A(1, B(0)), A.deserialize(json.dumps({'a': 1})))

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

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

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

当我们要反序列化一个之前版本的序列化结果时,我们希望能正确的反序列化并使用我们提供的默认值作为最终的反序列化值。

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

class ModelBase:@staticmethoddef is_normal_prop(obj, key):is_prop = isinstance(getattr(type(obj), key, None), property)is_constant = re.match('^[A-Z_0-9]+$', key)return not (key.startswith('__') or callable(getattr(obj, key)) or is_prop or is_constant)@staticmethoddef is_basic_type(value):return value is None or type(value) in [int, float, str, list, tuple, bool, dict]def _serialize_prop(self, name):value = getattr(self, name)if isinstance(value, (tuple, list)):try:json.dumps(value)return valueexcept Exception:return [v._as_dict() for v in value]return valuedef _as_dict(self):keys = dir(self)props = {}for key in keys:if not ModelBase.is_normal_prop(self, key):continuevalue = self._serialize_prop(key)if not (ModelBase.is_basic_type(value) or isinstance(value, ModelBase)):raise Exception('unkown value to serialize to dict: key={}, value={}'.format(key, value))props[key] = value if self.is_basic_type(value) else value._as_dict()return propsdef _short_prop(self, name):value = getattr(self, name)if isinstance(value, (tuple, list)):try:json.dumps(value)return valueexcept Exception:return [v._as_short_dict() for v in value]return valuedef _as_short_dict(self):keys = dir(self)props = {}for key in keys:if not ModelBase.is_normal_prop(self, key):continuevalue = self._short_prop(key)if not (ModelBase.is_basic_type(value) or isinstance(value, ModelBase)):raise Exception('unkown value to serialize to short dict: key={}, value={}'.format(key, value))props[key] = value if self.is_basic_type(value) else value._as_short_dict()return propsdef serialize(self):return json.dumps(self._as_dict(), ensure_ascii=False)def _deserialize_prop(self, name, deserialized):setattr(self, name, deserialized)@classmethoddef deserialize(cls, json_encoded):if json_encoded is None:return Noneimport inspectargs = 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_encodedkeys = dir(obj)for key in keys:if not ModelBase.is_normal_prop(obj, key):continueif key in data:obj._deserialize_prop(key, data[key])return objdef __str__(self):return self.serialize()def _prop_eq(self, name, value, value_other):return value == value_otherdef __eq__(self, other):if other is None or other.__class__ is not self.__class__:return Falsekeys = dir(self)for key in keys:if not ModelBase.is_normal_prop(self, key):continuevalue, value_other = getattr(self, key), getattr(other, key)if not (ModelBase.is_basic_type(value) or isinstance(value, ModelBase)):raise Exception('unsupported value to compare: key={}, value={}'.format(key, value))if value is None and value_other is None:continueif (value is None and value_other is not None) or (value is not None and value_other is None):return Falseif not self._prop_eq(key, value, value_other):return Falsereturn Truedef short_repr(self):return json.dumps(self._as_short_dict(), ensure_ascii=False)

为了更进一步提供支持,我们将最终的类命名为ModelBase,因为通常我们要序列化或反序列化的对象都是我们需要特殊对待的对象,且我们通常称其为模型,我们一般也会将其放在一个单独models模块中。

作为一个模型的基类,我们还添加了一些常用的特性,比如:

  1. 支持标准的格式化接口__str__,这样我们在使用'{}'.format(a)的时候,就可以得到一个更易于理解的输出

  2. 提供了一个缩短的序列化方式,在我们有时候不想直接输出某一个特别长的属性的时候很有用

  3. 提供了基于属性值的比较方法

  4. 自定义类的属性可以为基础的Python类型,或者由基础Python类型构成的list tuple dict

在使用这个类的时候,当然也是有一些限制的,主要的限制如下:

  1. 当某一属性为自定义类的类型的时候,需要子类覆盖实现_deserialize_prop方法为反序列化过程提供支持

  2. 当某一属性为由自定义类构成的一个list tuple dict复杂对象时,需要子类覆盖实现_deserialize_prop方法为反序列化过程提供支持

  3. 简单属性必须为python内置的基础类型,比如如果某一属性的类型为numpy.float64,序列化反序列化将不能正常工作

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

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

来源:华为云社区原创 作者:Bright Liao

#华为云·寻找黑马程序员# 如何实现一个优雅的Python的Json序列化库相关推荐

  1. 黑马程序员python笔记_#华为云·寻找黑马程序员# 如何实现一个优雅的Python的Json序列化库...

    [小宅按]在Python的世界里,将一个对象以json格式进行序列化或反序列化一直是一个问题.Python标准库里面提供了json序列化的工具,我们可以简单的用json.dumps来将一个对象序列化. ...

  2. 移动端开发语言的未来的猜想#华为云·寻找黑马程序员#

    [摘要] #华为云.寻找黑马程序员# 不管是最早的Phonegap,还是后来的React Native.Weex,Flutter,或者是各个公司自创开发语言,都是在不断寻求开发语言统一,从而解决ios ...

  3. 三伏天里小试牛刀andriod开发 #华为云·寻找黑马程序员#

    三伏天里小试牛刀andriod 开发 #华为云·寻找黑马程序员# 2019年07月,北京,三伏天,好热啊.越热自己还越懒得动换(肉身给的信号),但是做为产品经理/交互设计师的,总想着思考些什么(灵魂上 ...

  4. python让你再也不为文章配图与素材发愁,让高清图片占满你的硬盘! #华为云·寻找黑马程序员#

    欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动",获取华 ...

  5. 大型情感剧集Selenium:1_介绍 #华为云·寻找黑马程序员#

    学习selenium能做什么? 很多书籍.文章中是这么定义selenium的: Selenium 是开源的自动化测试工具,它主要是用于Web 应用程序的自动化测试,不只局限于此,同时支持所有基于web ...

  6. #华为云·寻找黑马程序员#【代码重构之路】如何“消除”if/else

    1. 背景 if/else是高级编程语言中最基础的功能,虽然 if/else 是必须的,但滥用 if/else,特别是各种大量的if/else嵌套,会对代码的可读性.可维护性造成很大伤害,对于阅读代码 ...

  7. 【带着canvas去流浪(13)】用Three.js制作简易的MARVEL片头动画(下)#华为云·寻找黑马程序员#

    [摘要] three.js实现的Web 3D字体模型动画 示例代码托管在:http://www.github.com/dashnowords/blogs 有了上一篇基础知识的储备,本节就来制作一下简易 ...

  8. 大型情感剧集Selenium:4_老中医教你(单/多/下拉框)选项定位 #华为云·寻找黑马程序员#

    欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动",获取华 ...

  9. Flask开发VIP版HttpServer #华为云·寻找黑马程序员#

    欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动",获取华 ...

最新文章

  1. 黄聪:VS2010开发T4模版引擎之基础入门
  2. 利用人体肤色从图像中分割出人体区域的OpenCV代码
  3. 利用 livy 远程提交 spark作业
  4. fastreport 上一行_创业天下全球总裁袁丽军一行莅临平川区 电商农特产扶贫展馆考察指导工作...
  5. LINQ学习——JOIN
  6. python sqlite connection
  7. TreeView 节点多选,对TreeView的遍历
  8. 虚拟机无法安装VMware Tools
  9. 人工智能产品经理视频教程
  10. 山石岩读丨前沿领域探析——汽车CAN总线协议详解及攻击面分析
  11. 基于i.mx6q平台的NES模拟器移植
  12. 文件是否存在 FileExists 方法
  13. Android 修改手机状态栏文字颜色
  14. 驳《驳〈论OIer谈恋爱的必要性〉》
  15. linux用户的主要配置文件,Linux用户和组的主要配置文件及其相关命令
  16. 微信账单怎么查?微信流水账单怎么打印
  17. mysql数据库被勒索删库怎么办
  18. QQ播放器更新界面设计
  19. C语言学习之路--C语言中的格式输出
  20. Vulnhub靶机渗透学习——DC-9

热门文章

  1. java快速压缩文件夹_如何使用java压缩文件夹本身
  2. java中volatile_java中volatile关键字的含义
  3. iwrite提交不了作业_“iWrite写作中心”使用全攻略
  4. Batch批量替换hosts
  5. .NET Core WebAPI Swagger使用
  6. 2017-2018-2 20179306 《网络攻防技术》第十周作业
  7. ASP.NET MVC 中@Html.Partial,@Html.Action,@Html.RenderPartial,@Html.RenderAction
  8. Java的equals()和==的区别
  9. xcode配置最新版opencv
  10. leetcode 70 python (动态规划)