Python自定义JSON序列化库实现:解决跨语言反序列化难题
本文探讨Python中自定义对象JSON序列化的痛点,对比标准库与第三方库的局限,提出一种无需侵入代码即可实现任意对象序列化的方案。该方案通过内省机制自动识别属性,支持嵌套类反序列化及属性缺失时的默认值处理,完美解决跨语言通信中类型信息缺失导致的反序列化失败问题。
前言
在Python开发中,将对象转换为JSON格式进行序列化或反序列化是一个常见挑战。虽然Python标准库提供了json模块,可以通过json.dumps轻松序列化对象,但该功能仅支持Python内置的基本数据类型。
Python
在Python中处理自定义对象的JSON序列化时,若直接使用标准库的json.dumps,面对非内置类型的自定义类,程序会抛出“Object of type A is not JSON serializable”的错误,导致序列化失败。
目前有多种方案可解决此问题,相关讨论众多。主要思路可归纳为两类较为可行的方案:
- 基于标准库接口扩展:通过继承Python标准json库中的JSONDecoder类,并自定义实现default方法,从而定义特定的序列化逻辑。
- 使用第三方库:例如jsonpickle、jsonweb、json-tricks等工具,它们提供了更广泛的序列化支持。
使用标准库接口的主要缺陷在于,开发者必须为每一个自定义类单独实现JSONDecoder的default接口,这导致代码复用性差,维护成本高。
相比之下,使用第三方库对现有代码几乎无侵入性。以jsonpickle为例,它基于pickle标准序列化库构建,能够像pickle一样序列化任意对象,无需修改任何现有代码即可实现序列化功能。
然而,观察这类第三方库的输出结果会发现,它们都会在生成的JSON中增加一个特殊属性来标记对象类型。这是因为Python是动态类型语言,在对象构建前无法确定属性类型。为了支持反序列化,这种类型标记似乎是必要的。
有人可能认为这并无大碍,但在跨语言通信场景中,这会成为显著障碍。例如,若Python后端API接收客户端发送的JSON请求,并希望在统一入口将JSON反序列化为Python对象,由于客户端无法获知服务器端的类型定义,JSON请求中自然无法包含类型信息。这导致依赖类型标记的类库在反序列化时无法正常工作。
是否存在一种更完善的实现方案?首先明确理想JSON序列化库应具备的需求:
- 能够简单序列化任意自定义对象,仅需添加一行代码,甚至无需任何额外代码。
- 序列化结果中不包含任何非预期的额外属性,保持JSON的纯净性。
- 支持按指定类型进行反序列化,能自动处理嵌套的自定义类。自定义类只需提供极简支持,或无需任何支持即可正常工作。
- 在反序列化过程中,能妥善处理属性不存在的情况。当新增属性时,可设置默认值,确保旧版本的序列化数据仍能正确反序列化。
若有一个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中特意添加了一个@property属性作为测试。
由于反序列化时框架无法预知对象属性的类型信息(如测试中的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的内省机制,从而自动识别对象的属性及其运行时类型。当然,这个简单的类仍有诸多待支持的功能,且使用上存在一定限制,例如:
- 当某一属性为自定义类类型时,子类需覆盖实现_deserialize_prop方法,以支持反序列化过程。
- 当某一属性为由自定义类构成的list、tuple或dict等复杂对象时,子类需覆盖实现_deserialize_prop方法,以支持反序列化过程。
- 简单属性必须为Python内置的基础类型。例如,若某一属性的类型为numpy.float64,则序列化与反序列化将无法正常工作。
尽管存在上述限制,但这恰恰要求我们在模型设计时保持克制,避免将对象设计得过于复杂。例如,若属性为dict类型,我们可以将该dict抽象为另一个自定义类型,并通过类型嵌套的方式来实现。
至此,这个基类已基本满足日常开发需求。当然,对于这个简单实现可能还有其他需求或问题,欢迎大家发现后留言交流。
总结
到此,关于利用Python实现JSON序列化库的文章介绍完毕。更多相关Python实现JSON序列化库的内容,请搜索本站以前的文章或继续浏览下面的相关文章。希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













