应通过封装解析函数递归计数+异常捕获,或使用pydantic的recursion_limit配置与字符串长度、对象/数组数量预检组合限深防爆。

JSON 解析时如何识别嵌套过深的结构
Python 默认的 json.loads() 不限制嵌套深度,遇到恶意构造的超深嵌套(比如 1000 层字典套字典),会触发 RecursionError 或耗尽内存。但这个错误往往出现在解析中途,无法提前拦截——你得在解析前或解析中主动设防。
关键不是等它崩,而是控制解析器行为:
- 用
json.loads()的parse_float/parse_int等钩子函数,在每次进入新对象或数组时计数嵌套层级 - 更稳妥的是改用
json.JSONDecoder并重写scan_once(不推荐)或直接用第三方库如json5或ijson流式解析 + 手动限深 - 最实用方案:封装一层解析函数,用递归计数 + 异常捕获兜底
用自定义 JSONDecoder 控制最大嵌套深度
Python 标准库允许传入自定义 json.JSONDecoder 实例,通过覆盖其内部方法实现深度控制。虽然不能直接 hook 到“进入对象”时刻,但可以利用 object_hook 和 parse_constant 配合线程局部变量来跟踪——不过更轻量的做法是用递归解析器替代。
一个简单可靠的模式:
立即学习“Python免费学习笔记(深入)”;
import json from contextlib import contextmanager@contextmanager def limited_depth(max_depth=100): orig = json._default_decoder.parse_object depth = [0]
def parse_object(s_and_o, **kw): depth[0] += 1 if depth[0] > max_depth: raise ValueError(f"JSON nested too deep: {depth[0]} > {max_depth}") try: return orig(s_and_o, **kw) finally: depth[0] -= 1 json._default_decoder.parse_object = parse_object try: yield finally: json._default_decoder.parse_object = orig使用
try: with limited_depth(50): data = json.loads('{"a": {"b": {"c": ... }}}') # 超过 50 层就抛 ValueError except ValueError as e: print(e)
⚠️ 注意:
json._default_decoder是私有 API,Python 版本升级可能失效;生产环境建议用jsons或pydantic做 schema 级校验,而非 patch 内部函数。用 pydantic v2 做安全反序列化(推荐生产用)
如果你已用
pydantic定义数据结构,它默认会对嵌套做软限制,并支持显式配置递归深度。v2 中可通过model_validate_json()+context控制解析行为:
-
model_validate_json()支持context参数,可传入自定义解析上下文 - 结合
BaseModel.model_config = {'recursion_limit': 64}可约束模型内字段递归展开深度 - 真正起作用的是底层
json.loads()调用前的预检 —— 所以仍需配合字符串长度、键数量等基础过滤
示例:
from pydantic import BaseModel import jsonclass Payload(BaseModel): data: dict
先粗筛:限制原始 JSON 字符串长度和对象/数组数量
def safe_load_json(raw: str) -> dict: if len(raw) > 1_048_576: # 1MB 上限 raise ValueError("JSON too large") if raw.count('{') > 1000 or raw.count('[') > 1000: raise ValueError("Too many objects/arrays") return json.loads(raw)
再进 pydantic 校验
try: payload = Payload.model_validate_json( safe_load_json(user_input), context={'max_nesting': 50} ) except (ValueError, TypeError, json.JSONDecodeError) as e: raise ValueError("Invalid or unsafe JSON") from e
为什么只限深度还不够?还得卡长度和键数
单纯限制嵌套深度防不住“宽炸弹”:比如一层里塞 10 万个 key,每个 value 是长字符串,照样吃光内存。JSON 炸弹本质是让解析器分配大量小对象,最终 OOM。
必须组合策略:
- 原始字符串长度上限(
len(raw) ) - 统计
{、[出现次数,间接反映对象/数组总数 - 解析后检查结果大小:
sys.getsizeof(result)或递归计算元素个数(慎用,可能再被炸) - Web 框架层前置过滤:FastAPI 的
Body(..., max_length=1024*1024),Flask 的request.get_data(cache=False, limit=1024*1024)
最易忽略的一点:很多服务把 JSON 当作可信输入,直接 json.loads(request.body),连长度都不验。其实第一道防线永远是 HTTP 层的 Content-Length 和 body 读取限制。










