Python json.loads 怎样处理重复键
来源:17golang原创
时间:2026-11-03 12:20:31 424浏览 收藏
Python 的 json.loads 默认允许同一对象中出现重复键,只保留最后一次出现的值。想把重复键当作输入错误,需要设置 object_pairs_hook,在有序键值对列表被合并成字典前检查成员名。
我在选择配置读取方案时,更在意一个字段究竟由谁决定:如果业务约定只允许一个值,就应该让生产配置的人看到冲突。下面以一段虚构的任务队列配置说明这种取舍,代码和结果描述均用于演示。
Python 官方文档:https://docs.python.org/3/library/json.html
先明确这次迁移改变的是接收规则
这里的“迁移”指把应用原来的宽松读取改为拒绝重复成员,不是宣称升级 Python 后默认规则改变。Python 文档对默认保留最后值和自定义钩子都有明确说明。JSON 标准建议对象成员名唯一,同时指出不同实现面对重复名称时可能采用不同策略。跨系统传递配置时,不能只凭一个解析器的结果推断所有接收方的理解。
我会先把入口分成两类:自己控制生成器的配置,直接修生产端;必须兼容外部历史文件的入口,先约定切换日期和错误处理。不要把拒绝重复键的变更偷偷塞进一次运行时升级,让调用者误以为以前能导入的内容仍然全部可用。
把默认覆盖与显式拒绝放在一起比较
| 接收策略 | 重复键的处理 | 适合的接口约定 |
|---|---|---|
| 默认解析 | 同一键保留最后值 | 已经明确接受覆盖的输入 |
| 拒绝重复键 | 钩子抛出异常 | 每个成员只能出现一次的配置 |
| 保留键值对 | 用键值对容器保存全部成员 | 需要分析原始成员的专用处理程序 |
以 {"workers":2,"workers":5} 为例,默认读取后字典中的 workers 为 5。改用本文的拒绝策略时,这份输入会失败,两个数字相同也不能通过。是否允许覆盖应由接口契约决定,而不是把“没有解析异常”当作数据没有歧义。

字典已经生成后就找不回重复记录
object_hook 收到的是已解码对象的字典;object_pairs_hook 收到有序键值对列表,返回值替代该对象的默认结果。两者同时配置时,后者优先。因此,不能指望在 object_hook 中查看字典长度,就知道之前有几个相同名称。
我更愿意让读取入口只负责一种成员策略,再在解析成功后调用业务转换函数。这能避免维护者误以为两个 hook 会依次执行。若原来依赖 object_hook 把对象转换成领域类型,就要把去重检查与转换显式组织进同一个 pairs 钩子,并重新考虑嵌套对象的转换条件。
另外,保留全部键值对会改变对象表示。使用简单列表保存 pairs 后,空对象和空数组可能都变成空列表;若业务要区分两者,需要明确的容器类型约定。本文选择返回字典,保持正常配置的使用方式。
用一个按对象生效的钩子拒绝重复键
下面的 unique_object 在每次回调中创建独立的 result,用 key in result 判断重复,再保存值。load_config 额外要求顶层为对象,这是示例应用自己的配置约定。它没有检查字段值类型或必填项,后续业务规则仍需另行实现。
import json
class DuplicateKeyError(ValueError):
"""输入对象包含重复成员名。"""
def unique_object(pairs):
# 每个 JSON 对象都有自己的字典,同名键可以存在于不同对象中。
result = {}
for key, value in pairs:
# 必须检查成员存在性,不能用值是否为 None 判断。
if key in result:
raise DuplicateKeyError("JSON 对象包含重复键")
result[key] = value
return result
def load_config(text):
# 钩子的返回值替代该对象的默认字典结果,嵌套对象也适用。
data = json.loads(text, object_pairs_hook=unique_object)
if not isinstance(data, dict):
raise ValueError("配置顶层必须是对象")
return data
if __name__ == "__main__":
# 故意重复的演示输入;读者可换成自己的小型配置文本。
text = '{"queue": "reports", "workers": 2, "workers": 5}'
try:
config = load_config(text)
except json.JSONDecodeError as exc:
# 语法错误带位置;不记录整段配置,以免暴露字段值。
print(f"JSON 语法错误:行 {exc.lineno},列 {exc.colno}")
except DuplicateKeyError as exc:
# 自定义异常与语法错误分开处理,不承诺提供完整字段路径。
print(str(exc))
except ValueError as exc:
print(str(exc))
else:
print(config)
上面的主程序应进入重复键异常分支;若移除第二个 workers 成员,解析成功后才会打印配置。这里只给出预期,未把示例描述为实际执行记录。错误消息刻意不回显完整键名或值;若产品需要精确字段路径,单靠这个钩子的参数并不够,不能凭空补出行列信息。

用小样本约定迁移后的结果
下面这些是接入方可以采用的预期样本,不是性能数据,也不是已经运行过的测试报告。检查时要保留原始 JSON 文本;若先交给另一个解析器转成字典,重复信息可能已被抹掉。
| 原始输入 | 本文入口的预期 | 原因 |
|---|---|---|
{"workers":2} | 接受 | 成员唯一,顶层为对象 |
{"workers":null,"workers":5} | 拒绝 | null 仍然代表该成员已经出现 |
{"task":{"workers":2,"workers":5}} | 拒绝 | 嵌套对象也经过同一钩子 |
{"left":{"workers":2},"right":{"workers":5}} | 接受 | 同名成员分属两个对象 |
{"name":"a","na\u006de":"b"} | 拒绝 | 转义解码后两个成员名相同 |
{"Mode":1,"mode":2} | 接受 | 本函数不进行大小写归一化 |
[] | 拒绝 | 应用要求顶层必须是对象 |
“转义后相同”与“业务认为相同”要分开:本文比较的是解析后的 Python 字符串,没有自动去空格、忽略大小写或做 Unicode 规范化。如果业务要合并这些名称,规则应该公开写进接口约定,并在规范化之后另行处理冲突。
分批接入,并把错误交给数据生产者
对我来说,这个方案最实际的代价是兼容性:它让过去被静默覆盖的输入开始报错。我会先用脱敏的小样本说明变化,再让各生产方修复生成逻辑;服务端把这类错误映射为可理解的输入错误,避免在失败后自动退回默认解析,否则接收规则又变回了宽松覆盖。
- 确定哪些入口接受原始文本,哪些入口已经拿到字典。
- 清点既有 object_hook 的对象转换职责,避免迁移后被忽略。
- 约定重复键、语法错误与业务字段错误的响应方式。
- 覆盖嵌套对象、null、转义名称和顶层数组这些小样本。
- 在上传或请求入口设置适合业务的输入大小限制;pairs 钩子本身不是流式解析器。
strict=True 会拒绝重复键吗?
不会。JSONDecoder 的 strict 参数控制字符串内部的控制字符接受规则,不能替代成员名检查。
重复的值也要报错吗?
不需要。{"primary":2,"backup":2} 的键不同。本文只拒绝同一对象里再次出现的相同键。
改成拒绝重复键就等于完整严格 JSON 吗?
不等于。数值扩展、编码和业务类型等问题有各自的处理规则。本函数的名字和接口说明应保持明确,只承诺重复键策略及顶层对象约束。
延伸阅读:Python 对象解码与重复成员说明:https://docs.python.org/3/library/json.html;JSON 成员唯一性的互操作说明:https://www.rfc-editor.org/rfc/rfc8259.html。
-
373 收藏
-
152 收藏
-
文章 · python教程 | 1小时前 | 标准库 · xml · Python教程 · Python xml 流式解析 ElementTree xml.etree.ElementTree iterparse339 收藏
-
文章 · python教程 | 1小时前 | SQLite · 事务管理 · Python教程 · Python 事务 上下文管理器 sqlite3 autocommit isolation_level Connection257 收藏
-
196 收藏
-
177 收藏
-
184 收藏
-
237 收藏
-
480 收藏
-
218 收藏
-
文章 · python教程 | 1小时前 | 格式化 · Template Python 3.14 t-string string.templatelib template strings Interpolation418 收藏
-
276 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习