Python的json序列化
引言
在开发中,经常使用json格式的数据来相互传递,json序列化和反序列化也是常规操作,在java中有各种第三方json库来实现,比如Gson,jackson,fastjson,后者是我经常在用的。而在Pyhon中如何实现呢,Python自带了json库,json序列化和反序列化主要有两个方法:
- dumps() : 序列化,将对象转换为json字符串
import json
person = {"name": "tom", "age": 20}
result = json.dumps(person)
print(result) # {"name": "tom", "age": 20}
print(type(result)) # <class 'str'>
值得一提的是dumps()方法还支持接收一个indent参数,参数类型为int类型,当传递这个参数将让数据变得更加美观易读
result = json.dumps(person,indent=4)
print(result)
"""
{
"name": "tom",
"age": 20
}
"""
- loads() :反序列化,将json字符串转换为字典
p = json.loads(result)
print(p) # {'name': 'tom', 'age': 20}
print(type(p)) # <class 'dict'>
序列化自定义类
class Person:
def __init__(self, name, age) -> None:
self.name = name
self.age = age
p = Person("tom", 20)
result = json.dumps(p) # TypeError: Object of type Person is not JSON serializable
如上面代码所示,在尝试对一个自定义类的实例json序列化时,会报TypeError错误,那为什么会这样呢
序列化支持
在Python中,一个对象是否可以被序列化取决于它的类型和内容。
以下是一些常见的对象和类型,以及它们是否可以被序列化
- 基本数据类型
int, float, str, bool, None,这些都是可以直接序列化的 - 序列化支持的对象
dict,list,tuple,set,这些对象也是可以直接序列化的 - 不可序列化的对象
- 方法:任何类型的方法都不能被序列化。
- 类:类本身不能被序列化,但是类的实例可以被序列化。
- 文件描述符:如打开的文件或网络连接等,这些对象通常是不可序列化的。
- 循环引用:如果对象之间存在循环引用,则这些对象也不能被序列化。
- 模块和函数
模块和函数通常是不可序列化的,因为它们是Python运行时的抽象,不是可存储的 - 复杂的对象
例如,自定义类、自定义的集合类、装饰器等,这些对象通常需要实现序列化支持才能被序列化
自定义类序列化
在面向对象中,我们通常将数据结构类型定义成一个类,而在python中,自定义类本身是不支持序列化的,需要实现序列化支持,才能被序列化。
如何实现:
字典对象是支持序列化的,而我们自定义的类会有一个__dict__的属性(从object继承)这个属性是一个包含实例对象属性值的字典对象,可以通过该属性获取字典对象并传给json.dumps()
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("tom", 20)
print(p.__dict__) # {'name': 'tom', 'age': 20}
print(type(p.__dict__)) # <class 'dict'>
result = json.dumps(p.__dict__)
print(result) # {"name": "tom", "age": 20}
print(type(result)) # <class 'str'>
目前仅限于简单的自定义类,如果涉及到类的某一个属性的数据类型指向了其他自定义类,那么json.dumps(obj.dict)方式将不再适用,会报TypeError错误
class Feature:
def __init__(self, height, weight) -> None:
self.height = height
self.weight = weight
class Person:
def __init__(self, name, age, feature):
self.name = name
self.age = age
self.feature = feature
feature = Feature(175, 125)
p = Person("tom", 20, feature)
result = json.dumps(p.__dict__) # TypeError: Object of type Feature is not JSON serializable
自定义JSONEncoder
分析
从下面dupms方法源码可以看出,dumps方法序列化核心主要是JSONEncoder对象,我们可以通过继承JSONEncoder并重写encode方法实现自定义JSONEncoder,并将自定义的encoder传给dumps方法,来达到序列化的目的
# dumps方法源码
def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
default=None, sort_keys=False, **kw):
if (not skipkeys and ensure_ascii and
check_circular and allow_nan and
cls is None and indent is None and separators is None and
default is None and not sort_keys and not kw):
return _default_encoder.encode(obj)
if cls is None:
cls = JSONEncoder
return cls(
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
separators=separators, default=default, sort_keys=sort_keys,
**kw).encode(obj)
实现思路
利用Python对象的特性将实例对象转换为字典对象,再调用父类方法序列化字典对象
from typing import override
import json
class CustomJSONEncoder(json.encoder.JSONEncoder):
@override
def encode(self, obj) -> str:
dct = self.convert(obj)
return super().encode(dct)
def convert(self, obj):
if hasattr(obj, "__dict__"):
dct = obj.__dict__
for k, v in dct.items():
f = self.convert(v)
dct[k] = f
return dct
if isinstance(obj, (list, set, tuple)):
seq = []
for e in obj:
seq.append(self.convert(e))
return seq
return obj
class Feature:
def __init__(self, height, weight) -> None:
self.height = height
self.weight = weight
class Person:
def __init__(
self,
name,
age,
feature=None,
favorites=[],
children=[],
):
self.name = name
self.age = age
self.feature = feature
self.favorites = favorites
self.children = children
james = Person("James", 10)
jimmy = Person("Jimmy", 7)
favoites = ("打篮球", "鸡胸肉")
feature = Feature(175, 125)
peson = Person("tom", 32, feature, favoites, children=[james, jimmy])
result = json.dumps(peson, cls=CustomJSONEncoder, ensure_ascii=False)
print(result)
"""
{
"name": "tom",
"age": 32,
"feature": {
"height": 175,
"weight": 125
},
"favorites": [
"打篮球",
"鸡胸肉"
],
"children": [
{
"name": "James",
"age": 10,
"feature": null,
"favorites": [],
"children": []
},
{
"name": "Jimmy",
"age": 7,
"feature": null,
"favorites": [],
"children": []
}
]
}
"""
print(type(result)) # <class 'str'>
浙公网安备 33010602011771号