json.loads() 没报错,输入就可靠吗:重复键与非有限数的三道检查
摘要:Python 的 JSON 解析成功,只说明输入被当前配置接受。本文用标准库合成实验说明重复键、NaN 常量和浮点溢出如何进入结果,并给出一个拒绝这些情况的短例子。
配置或接口数据出现两个同名字段时,程序该采用哪一个?如果没有明确约定,解析器的默认行为就可能悄悄替业务作决定。只加一个看似严格的参数,也未必改变这个结果。
在字典形成前检查重复键
本次实验把 {"count":1,"count":2} 交给 json.loads(),得到的字典只有 count: 2。设置 strict=True 结果相同。这里的 strict 主要控制字符串内未经转义的控制字符,并不表示启用一套完整的数据验证规则。
等解析结果变成字典,再检查键是否唯一已经太晚:前一个值不见了。object_pairs_hook 可以在每个对象转为字典前取得键值对列表,逐个检查后再组装。钩子也作用于嵌套对象,因此能发现内层重复。
比较的应是解码后的键名。实验中,"a" 与 "\u0061" 被识别为同一个键;在原始文本上数引号或搜索重复片段,容易漏掉这种写法。不同子对象各有一个 id 则不会冲突,因为检查范围是单个对象。
有些协议明确采用后值覆盖前值,也有场景需要保留所有键值对。先确定输入约定,再选择策略。本例选择整次拒绝,是为了避免输出字典丢掉重复信息后,下游无法区分“原文只有一个字段”和“发生过覆盖”。它没有替任何现有接口重新定义规则。
非标准常量和数值溢出要分开挡
默认解析还接受 NaN、Infinity 和 -Infinity,本次均得到相应的非有限浮点值。若输入契约不允许这些常量,可以通过 parse_constant 抛出异常;它收到的是对应常量的文本。
但 1e400 走的是另一条路。它符合 JSON 数字的语法,本次默认转换为浮点数后成为正无穷大,parse_constant 不会因此被调用。要约束转换结果,可以用 parse_float 接管含小数或指数的数字,再用 math.isfinite() 检查。
常量的拒绝和浮点范围的拒绝也应保留区别。前者表示出现了本例不接受的字面写法,后者表示数字虽然能按语法读取,却不能成为有限浮点值。把两者都记作“文件格式错误”,会让使用者反复修改标点,而忽略真正需要调整的数值或数据约定。
下面是完整可运行的示例:
import json
import math
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError(f"重复键:{key}")
result[key] = value
return result
def finite_float(token):
value = float(token)
if not math.isfinite(value):
raise ValueError("超出有限浮点范围")
return value
def reject_constant(token):
raise ValueError(f"不接受常量:{token}")
def load_checked(text):
return json.loads(text, object_pairs_hook=unique_object,
parse_constant=reject_constant,
parse_float=finite_float)
if __name__ == "__main__":
for text in ('{"count":2}', '{"count":1,"count":2}',
'{"rate":NaN}', '{"rate":1e400}'):
try:
print("接受", load_checked(text))
except ValueError as exc:
print("拒绝", exc)
本次运行依次接受正常对象,拒绝重复键、NaN 和溢出数值。示例捕获异常是为了展示结果;真实调用方应把拒绝明确传给上层,不能捕获后继续按有效数据处理。
记录错误时还应保留失败阶段和对应输入位置。示例的自定义异常只有简短消息,没有实现字段路径追踪;嵌套对象很深时,需要另行补充可定位的诊断,不能直接把示例当作完整错误报告模块。
有限浮点值仍有精度与业务边界
这三个钩子没有组成通用输入验证器。实验中,1e-400 转成 0.0 后仍被接受,因为零是有限数;有限不等于精确,也不等于满足字段的业务范围。需要保存十进制原值时,应另行选择数值表示与运算规则。
同样,数组和布尔值仍能通过本例。若接口要求顶层必须是对象、count 必须为非负整数,就要在解析后分别检查结构与类型。输入字节上限、嵌套深度和资源消耗也需要单独设计,不能由几组正常样例推导出安全保证。
2026年11月4日,本实验在 Windows、Python 3.12.14 下运行21组预期观察,另执行文中代码,原始输出、退出码及文件指纹均已本地保存。预期拒绝用例退出2,表示拒绝符合本实验契约。全部输入为合成数据,仅验证所列解析行为,未接入云服务或生产系统。
- 点赞
- 收藏
- 关注作者
评论(0)