Python 的 JSON 中如果出现两次相同的键会怎么样?尝试覆盖和重复检测

プログラミング・Web開発カテゴリを表すパンダのイラスト 编程・Web开发

关于本文
本文是通过利用生成式 AI 的自动化生成流程创建的。参考了 Python 官方的 json 模块规范,并使用虚拟的 JSON 字符串来解释重复键的处理方式。

验证状态:📘 已确认官方规范・未在实机上验证所载代码

Python 的 JSON 中如果出现两次相同的键会怎么样?尝试覆盖和重复检测

读取了 JSON 配置后,有时会发现先前写入的值似乎消失了。例如,如果虚拟设置中同时存在 "mode": "test" 和 "mode": "live",究竟会使用哪一个呢?当 Python 标准库的 json.loads() 将其转换为普通字典时,会保留同名字段的最后一个值。仅凭普通的字典,无法恢复存在重复这一事实。

这种行为并不意味着“JSON 读取失败”。问题在于,即使输入方式含糊不清,程序也会采用最后一个值并继续处理。如果要安全地处理配置值或 API 接收的数据,有一种方法可以在它们变成字典之前检查是否出现了相同的名称。

目标环境与测试范围

项目内容
语言Python 3 系列,标准库 json
准备工具Python 交互式环境或练习脚本
输入仅限文章中的虚拟 JSON 字符串
文件与通信无文件读写,无网络通信
验证状态参考 Python 官方规范。未在实机上运行所列示例

您无需直接重写实际的配置文件或业务数据。首先仅使用字符串,观察值的变化方式。在将此方法引入现有系统的配置时,请同时确认发送方和接收方的规范,以及发生重复时是否应视为错误的契约。

先做一个小实验:保留最后一个值

在以下代码中,相同的 mode 出现了两次。显示的值是基于 Python 官方文档中所述默认行为的预期结果。

import json

raw = '{"mode":"test","mode":"live","retry":2}'
data = json.loads(raw)
print(data)
print(data["mode"])

期望的字典是 {'mode': 'live', 'retry': 2},第二行的值是 live。最初的 test 被覆盖到了字典的相同键中,因此不会保留。这里 json.loads() 不抛出异常也很重要。即使是语法上可读的 JSON,也未必能满足应用程序所期望的唯一性。

例如,如果重试次数被重复定义为 2 和 20,就会成为漏掉错误配置的原因。如果监控屏幕上只显示最终值,就会变得难以查明是在输入源的哪个地方发生了重复。特别是在合并多个配置的处理以及手动编辑模板的工序中,我们希望考虑到重复编写相同键的可能性。

为什么读取后很难发现

JSON 对象是名称与值的键值对结构。然而,Python 的普通字典不会同时保存两个相同键的值。如果在转换过程中采用后面的值,原先排列中的重复部分就会丢失。

下图展示了从原始 JSON 字符串到字典的信息变化。请注意左边的两个 mode 在右侧合并成了一个。

flowchart TD
    A["JSON文字列: mode=test, mode=live"] --> B{"読み込み方法"}
    B -->|通常のloads| C["辞書: mode=live"]
    B -->|object_pairs_hook| D["組の列: mode=test, mode=live"]
    D --> E["重複を検査して拒否できる"]
    C --> F["元の重複は判別しにくい"]

通过图右侧的方法,可以在合并为字典之前接收名称和值的组合。Python 的 object_pairs_hook 是按顺序接收 JSON 对象键值对的机制。它不仅能用来创建 dict,还可以通过自定义函数来检查重复。这里我们不保存或删除文件,而是在发现异常输入时直接引发异常。

发现重复时明确中止

以下函数会按顺序检查接收到的键值对,如果出现已经注册过的键,就会抛出 ValueError。如果直到最后都没有重复,则返回字典。

import json

def reject_duplicate_keys(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError(f"duplicate JSON key: {key}")
        result[key] = value
    return result

raw = '{"mode":"test","mode":"live","retry":2}'
try:
    parsed = json.loads(raw, object_pairs_hook=reject_duplicate_keys)
    print(parsed)
except ValueError as exc:
    print(type(exc).__name__, str(exc))

在此例中,预计会显示包含 duplicate JSON key: mode 的错误信息。在调用函数时,pairs 是键值对的序列。如果发现重复,程序会在完成字典转换前停止,因此不会默默地采用后面的值。

这里的设计决策不是“优先保留哪个值”,而是“如果输入模糊是否予以拒绝”。如果是涉及重要配置或访问控制的值,比起擅自决定以前面的值为准还是以后的值为准,要求配置创建者进行修正往往是更安全的做法。不过,如果现有系统允许重复,突然引入此项检查将会影响兼容性,因此请先调查接收数据的实际情况。

改变一个条件以观察差异

从先前的字符串中删除第二个 mode,并将其修改为 '{"mode":"test","retry":2}'。使用相同的检查函数,这次预计会返回一个字典。改变的仅仅是有无重复。由于无需更改检查处理的代码即可对比正常和异常情况,因此更容易理解该函数禁止了什么。

我们再考虑一下大小写不同的 "mode" 和 "Mode"。由于此函数直接比较 Python 的字符串键,因此它们会被视为不同的名称。如果业务上希望将其视为同一项目,则需要在应用程序端另行制定规范化规则。但是,切勿混淆 JSON 的标准键比较与业务本身对大小写的忽略。

此外,嵌套的JSON对象会怎么样呢?object_pairs_hook 会在处理每个JSON对象时被调用,因此即使内层出现重复也会被检测到。另一方面,不同的对象中各自含有一个相同的 mode 并不属于重复错误。重复的判定范围不是整个JSON文档,而是各个对象的内部。

失败时的排查:原因并非只有重复

json.loads() 本身抛出 JSONDecodeError 时,请首先检查引号、逗号、大括号等JSON语法。例如,Python的标准解码器不允许JSON末尾带有逗号。有时在到达重复键检测函数之前就会发生语法错误。不要将语法错误和重复键错误统称为“JSON损坏”,而应分门别类地记录下来,以便明确需要修改输入的哪个部分。

object_pairs_hook 和 object_hook 名字相似,但它们接收的信息不同。后者接收的是已转换为普通字典后的对象,因此不适合用于检查原始同名字段的用途。官方规范中也规定,当同时指定这两者时,object_pairs_hook 会具有更高的优先级。当您想要寻找重复项时,务必清楚函数是在哪个阶段被调用的。

对于巨大的JSON或可疑的外部输入,仅靠重复检测并不能保证安全。极大的数据量或过深的嵌套会导致处理时间和内存消耗问题。接收大小上限、超时、允许的条目、值的类型以及架构验证都是必不可少的。本次介绍的函数只是针对重复名称这一特定问题的对策,并不是万能的JSON验证器。

如果在业务中使用,应如何处理检测结果

例如,在审查配置的工作中,一种可行的流程是:首先保存原始JSON,如果重复检查发现问题则暂停导入,修正后重新检查。重要的不是在覆盖原始输入后再去调查错误,而是能够追踪修改前的内容。如果将整个JSON输出到公开日志中,会有泄露机密值的风险,因此在实际业务中,请将其严格限制在错误类型和安全的条目名称等必需的最小信息范围内。

这种思路同样适用于API接收到的JSON。不过,并非所有服务都会拒绝重复键。如果发送方和接收方的解释不一致,就可能会与签名验证、缓存、审计等其他处理产生冲突。在重要的系统对接中,最好在接口规范中明确规定“如果存在多个相同的键则报错”,并在测试数据中同时准备正常用例和重复用例。

如果只是人工确认简单的配置,审查并修改原始字符串的方法有时就足够了。另一方面,如果要每天自动处理大量的JSON,那么在接收入口处机械性地拒绝重复值的价值就会大大增加。无论选择哪种方式,通过本实验得出的重要发现是:切勿仅凭读取后的字典就断言“输入中没有重复项”。

总结

Python的 json.loads() 通常会采用重复键中的最后一个值。如果您想找出重复项,请使用能够接收转换为字典之前的键值对的 object_pairs_hook。输入的唯一性、语法的正确性、值的类型、文件大小以及机密性是需要分别确认的事项。请在通过安全的小示例理解了它们之间的区别后,再将其应用于业务的输入契约中。

官方文档

文档信息

文章??
Python 的 JSON 中如果出现两次相同的键会怎么样?尝试覆盖和重复检测
?布日期
更新日期
来源
https://papanda925.com/?p=18511&lang=zh

?可: ?于本站?有相??利的正文及原??表,除非?有?明,可依据 CC BY 4.0 使用。本文可能包含使用生成式AI?建或??的内容。若代??有?可声明,或?接的GitHub???定了?可,?代?以??可?准。引用内容、第三方?料、?片及商?不在本?可范?内。 使用政策

标题和URL已复制