JSON 校验前值就变了:在解析边界拒绝重复字段
导入接口收到同一个键两次,解析结果只留下后一个值。下游 schema 校验时,原来那次字段出现已从对象里消失。要追查这类数据,需要在把键值列表折叠成字典之前检查重复项。
1. 在解析器里保留字段出现顺序
保存为 strict_json_lab.py,执行 python strict_json_lab.py。
import json
from decimal import Decimal
def reject_duplicates(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError(f"duplicate JSON key: {key!r}")
result[key] = value
return result
def reject_constant(token):
raise ValueError(f"invalid JSON number: {token}")
def parse_document(text):
return json.loads(text, object_pairs_hook=reject_duplicates,
parse_constant=reject_constant, parse_float=Decimal)
ambiguous = '{"order":{"amount":1,"amount":2}}'
assert json.loads(ambiguous)["order"]["amount"] == 2
print("default parser nested amount:", json.loads(ambiguous)["order"]["amount"])
for text, expected in [
(ambiguous, "duplicate JSON key"),
(r'{"x":1,"\u0078":2}', "duplicate JSON key"),
('{"amount":NaN}', "invalid JSON number"),
]:
try:
parse_document(text)
except ValueError as error:
assert str(error).startswith(expected)
print(str(error))
else:
raise AssertionError(text)
valid = parse_document('{"amount":0.10}')
assert valid == {"amount": Decimal("0.10")}
print("valid decimal:", valid["amount"])
本地实测输出如下。
default parser nested amount: 2
duplicate JSON key: 'amount'
duplicate JSON key: 'x'
invalid JSON number: NaN
valid decimal: 0.10
2. 解析、字段校验和业务规则各管一层
| 层级 | 可以捕获的问题 |
|---|---|
| HTTP 入口 | 请求过大、媒体类型错误 |
| JSON 解析 | 文本残缺、重复键、NaN 和 Infinity |
| 结构校验 | 缺字段、未知字段、类型不符 |
| 业务校验 | 金额越界、对象所属租户不符 |
RFC 8259 建议对象内名称唯一,不同实现面对重复键的行为可能不同。本例把拒绝定为 API 策略,键在转义解码后比较,所以 x 和 Unicode 转义写出的 x 冲突。若服务还有 Unicode 搜索键规则,应在字段含义明确后另行应用。
parse_float=Decimal 只改变非整数数字的解析类型。金额需要单独校验位数和范围,序列化时也要明确输出契约。数据较大时,先限制输入字节数和可接受的嵌套程度,避免把整份异常内容写入日志。
小结
重复键检查放在 JSON 解析阶段。测试原始请求体而非预先构造的字典,并覆盖嵌套对象及转义后重复的成员。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| RFC 8259 | https://www.rfc-editor.org/rfc/rfc8259.html | 对象成员名称与数字语法 |
| Python JSON | https://docs.python.org/3.11/library/json.html | 对象钩子、默认重复键处理和 parse_constant |
核验说明
- Python 3.11.9 验证嵌套字段覆盖、转义键重复、非标准数字和十进制解析。示例仅覆盖解析层。