json.loads() 没报错,输入就可靠吗:重复键与非有限数的三道检查

举报
yd_294604443 发表于 2026/11/04 09:36:34 2026/11/04
【摘要】 Python 的 JSON 解析成功,只说明输入被当前配置接受。本文用标准库合成实验说明重复键、NaN 常量和浮点溢出如何进入结果,并给出一个拒绝这些情况的短例子。

摘要: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,表示拒绝符合本实验契约。全部输入为合成数据,仅验证所列解析行为,未接入云服务或生产系统。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。