pin_drop当前位置:知识文库 ❯ 图文
JSONDecoder详解 - Python自定义反序列化完整指南
概述
json.JSONDecoder 是 json 模块中用于 JSON 解码的基类,它定义了将 JSON 字符串反序列化为 Python 对象的默认行为。通过继承 JSONDecoder 并重写 object_hook 或 object_pairs_hook 方法,可以扩展解码器以支持自定义类型的还原(如将 ISO 日期字符串转回 datetime 对象)。JSONDecoder 是 JSONEncoder 的配套组件,两者配合可实现完整的自定义类型序列化/反序列化循环。
语法
代码示例
import json
class CustomDecoder(json.JSONDecoder):
def __init__(self, **kwargs):
kwargs['object_hook'] = self.object_hook
super().__init__(**kwargs)
def object_hook(self, obj):
# 自定义解码逻辑
return obj
# 使用自定义解码器
data = json.loads(json_str, cls=CustomDecoder)参数说明
JSONDecoder构造函数
常用方法
返回值
-
decode():返回解析后的 Python 对象
-
raw_decode():返回
(python_object, end_index)元组
代码示例
示例1:自动还原日期类型
代码示例
import json
from datetime import datetime, date
class DateTimeDecoder(json.JSONDecoder):
"""自动还原datetime和date的解码器"""
def __init__(self, **kwargs):
kwargs['object_hook'] = self._hook
super().__init__(**kwargs)
def _hook(self, obj):
for key, value in obj.items():
if isinstance(value, str):
# 尝试解析ISO日期时间
if 'T' in value:
try:
obj[key] = datetime.fromisoformat(value)
except ValueError:
pass
# 尝试解析纯日期
elif len(value) == 10 and value.count('-') == 2:
try:
obj[key] = date.fromisoformat(value)
except ValueError:
pass
return obj
# 编码
data = {"event": "会议", "time": "2024-01-15T14:30:00", "date": "2024-06-30"}
json_str = json.dumps(data, ensure_ascii=False)
# 解码
result = json.loads(json_str, cls=DateTimeDecoder)
print(f"time类型: {type(result['time']).__name__}")
print(f"time值: {result['time']}")
print(f"date类型: {type(result['date']).__name__}")
print(f"date值: {result['date']}")输出:
代码示例
time类型: datetime
time值: 2024-01-15 14:30:00
date类型: date
date值: 2024-06-30示例2:基于类型标记的解码器
代码示例
import json
from datetime import datetime, date
from decimal import Decimal
class TypedDecoder(json.JSONDecoder):
"""基于类型标记的解码器,与UniversalEncoder配套"""
TYPE_KEYS = {
'__datetime__': lambda v: datetime.fromisoformat(v),
'__date__': lambda v: date.fromisoformat(v),
'__decimal__': lambda v: Decimal(v),
'__set__': lambda v: set(v),
}
def __init__(self, **kwargs):
kwargs['object_hook'] = self._hook
super().__init__(**kwargs)
def _hook(self, obj):
# 检查是否为类型标记对象
for type_key, converter in self.TYPE_KEYS.items():
if type_key in obj and len(obj) == 1:
return converter(obj[type_key])
return obj
# 编码+解码完整循环
class TypedEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return {"__datetime__": obj.isoformat()}
elif isinstance(obj, date):
return {"__date__": obj.isoformat()}
elif isinstance(obj, Decimal):
return {"__decimal__": str(obj)}
elif isinstance(obj, set):
return {"__set__": list(obj)}
return super().default(obj)
original = {
"created": datetime(2024, 1, 15, 14, 30),
"deadline": date(2024, 6, 30),
"price": Decimal("99.99"),
"tags": {"python", "json"},
}
# 序列化
json_str = json.dumps(original, cls=TypedEncoder, ensure_ascii=False)
print("JSON:", json_str[:80], "...")
# 反序列化
restored = json.loads(json_str, cls=TypedDecoder)
print(f"\ncreated类型: {type(restored['created']).__name__}")
print(f"price类型: {type(restored['price']).__name__}")
print(f"tags类型: {type(restored['tags']).__name__}")输出:
代码示例
JSON: {"created": {"__datetime__": "2024-01-15T14:30:00"}, "deadline": {"__date__": "2024-0 ...
created类型: datetime
price类型: Decimal
tags类型: set示例3:raw_decode处理混合JSON流
代码示例
import json
def parse_json_stream(stream_str):
"""解析包含多个JSON对象的字符串流"""
decoder = json.JSONDecoder()
results = []
idx = 0
while idx < len(stream_str):
# 跳过空白
while idx < len(stream_str) and stream_str[idx].isspace():
idx += 1
if idx >= len(stream_str):
break
try:
obj, end = decoder.raw_decode(stream_str, idx)
results.append(obj)
idx = end
except json.JSONDecodeError as e:
print(f"解析错误位置 {idx}: {e}")
break
return results
# 测试:连续的JSON对象
stream = '{"a": 1} {"b": 2} {"c": 3}'
results = parse_json_stream(stream)
print(f"解析出 {len(results)} 个对象:")
for i, obj in enumerate(results, 1):
print(f" 对象{i}: {obj}")输出:
代码示例
解析出 3 个对象:
对象1: {'a': 1}
对象2: {'b': 2}
对象3: {'c': 3}实际应用场景
-
类型还原:与自定义编码器配合,实现特定类型的完整序列化/反序列化循环
-
JSON 流解析:使用
raw_decode()解析包含多个 JSON 对象的数据流 -
数据验证:在解码过程中验证数据格式和业务规则
注意事项
注意1:
object_hook在每个 JSON object 解码后被调用,嵌套对象从内到外依次触发。外层对象接收到的是已经过 hook 处理的内层对象。
注意2:基于类型标记的解码方式不是 JSON 标准做法,会与其他 JSON 工具不兼容。仅在内部系统间使用。
注意3:
raw_decode()不会跳过前导空白,需手动处理。它返回的结束位置是 JSON 对象后的第一个字符位置。
提示:
object_pairs_hook比object_hook更强大,它接收键值对列表而非字典,可以检测重复键、保留插入顺序或实现有序字典。
相关方法对比
小结
-
JSONDecoder通过继承和自定义object_hook实现自定义类型的还原 -
基于类型标记的编解码方案可实现完整的序列化/反序列化循环
-
raw_decode()可用于解析包含多个 JSON 对象的数据流 -
类型标记方案不是 JSON 标准,仅在内部系统间使用,对外接口应使用标准 JSON 格式
练习题
练习1
编写一个 StrictDecoder,在解码时检查所有字符串值是否为合法的 UTF-8,对非法编码抛出异常。
练习2
编写一个 DuplicateKeyDecoder,使用 object_pairs_hook 检测 JSON 中的重复键,并报告重复的键名。
练习3
使用 raw_decode() 实现一个 JSONL 文件解析器,逐个解析文件中的 JSON 对象,并统计总数。
常见问题
JSONDecoder和JSONEncoder如何配合使用?
使用类型标记方案:编码器在序列化时用特殊键名(如__datetime__)标记类型,解码器在反序列化时识别这些标记并还原为原始类型,实现完整的序列化/反序列化循环。
object_hook和object_pairs_hook有什么区别?
object_hook接收解码后的字典作为参数;object_pairs_hook接收键值对列表,可以在字典构建前处理数据,支持检测重复键和保留插入顺序,功能更强大。
raw_decode()的使用场景是什么?
raw_decode()用于解析包含多个连续JSON对象的字符串流,每次解析一个对象并返回结束位置,适合处理JSON流式数据或拼接的JSON内容。
为什么类型标记方案不是JSON标准做法?
JSON标准只定义了基本数据类型(字符串、数字、布尔、数组、对象、null),没有类型标记的概念。使用__datetime__等标记会导致与其他JSON工具不兼容,仅适合内部系统。
本文涉及AI创作
内容由AI创作,请仔细甄别