JSON API 与校验 · 后端开发
输入有问题时,API 应该怎么做?
课程目录为什么需要它
任何东西都可能发到你的服务器上:拼写错误、缺字段、该填数字的地方填了文字,或者有人故意想把它搞坏。会检查输入、把每个问题说清楚的 API,用起来很舒服;崩溃或者悄悄存下垃圾数据的 API,会在离真正原因很远的地方引发 bug。
直觉理解
创建东西的请求,每次都走同样的步骤:
- 解析:用
json.loads把 JSON 文字变成 Python 数据。不是合法 JSON →400 {"error": "invalid JSON"}。 - 校验:检查每个字段:有没有?类型对不对?在不在范围内?把所有问题收进一个列表,让客户端一次改完 →
400 {"errors": [...]}。 - 执行:存起来。
- 响应:
201,带上新对象,包括服务器给它分配的id。
校验就是一个普通函数:输入数据,输出错误列表。空列表就表示合法。
永远不要相信客户端:它不一定是你的应用。即使网页上检查过,服务器也要再检查一遍。
定义
JSON 请求体
The data a client sends with POST or PUT, as JSON text. json.loads turns it into dicts and lists.客户端在 POST 或 PUT 时发送的数据,是 JSON 文字。json.loads 把它变成字典和列表。
校验
Checking every field of the input before using it, and reporting what's wrong.使用输入之前检查每个字段,并报告哪里有问题。
示例
文字变数据,再变回去
动手试试json.loads 负责解析,json.dumps 负责写出,坏 JSON 会抛出一个可以捕获的错误。
import json
data = json.loads('{"name": "Ada", "tags": ["math", "code"]}')
print(data["tags"][0])
print(json.dumps({"ok": True, "count": 2}))
try:
json.loads("{name: Ada}")
except json.JSONDecodeError as e:
print("bad JSON:", e.msg)math
{"ok": true, "count": 2}
bad JSON: Expecting property name enclosed in double quotes
- L3JSON 对象变成字典,数组变成列表。
- L5Python 的 True 变成 JSON 的 true。
{"ok": true, "count": 2} - L8JSON 里的键必须用双引号,所以这里失败了。
解析、校验、创建
动手试试一个创建请求的完整流程。
import json
USERS = []
def validate(data):
errors = []
if not isinstance(data.get("name"), str) or not data.get("name"):
errors.append("name is required")
if not isinstance(data.get("age"), int):
errors.append("age must be a whole number")
return errors
def create_user(body):
try:
data = json.loads(body)
except json.JSONDecodeError:
return 400, {"error": "invalid JSON"}
errors = validate(data)
if errors:
return 400, {"errors": errors}
user = {"id": len(USERS) + 1, "name": data["name"], "age": data["age"]}
USERS.append(user)
return 201, user
print(create_user('{"name": "Ada", "age": 36}'))
print(create_user('{"name": "", "age": "36"}'))
print(create_user('not json'))(201, {'id': 1, 'name': 'Ada', 'age': 36})
(400, {'errors': ['name is required', 'age must be a whole number']})
(400, {'error': 'invalid JSON'})
- L5收集所有问题,而不只是第一个。
- L15第 1 步:解析。坏 JSON 在这里就以 400 结束。
- L18第 2 步:校验。
- L22第 3 步:保存,id 由服务器决定。
- L23第 4 步:201 和新用户。
常见错误
遇到第一个错误就停下
如果客户端有三个错误,一次只报一个,就要来回三次。一次全部收集。
用 `if data["age"]` 来检查
缺少这个键会抛出 KeyError(变成 500),而且 age 为 0 也算 false。用 data.get("age") 和 isinstance。
True 也是 int
在 Python 里,isinstance(True, int) 是 True。如果一个字段必须是真正的数字,还要排除布尔值。
小测验
1. 客户端发送 `{"qty": "three"}`,但 qty 必须是数字。哪个响应合适?
查看答案
答案:A
这是客户端能改正的错误:4xx,带一条有用的消息。
2. 这段代码会输出什么?
import json
print(json.loads('[1, "2", true, null]'))查看答案
答案:A
JSON 的 true 和 null 变成 Python 的 True 和 None;"2" 还是字符串。
动手练习
3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。
- 预测输出三个请求
准确写出它打印的内容。
- 改需求更严格的年龄检查
validate放过了不合理的年龄:-5、300、True都能通过。修改它,要求age是 0 到 150 之间的int(不能是布尔值),错误信息是"age must be a whole number from 0 to 150"。保留对 name 的检查。 - 从零写创建订单
写一个
create_order(body),body是 JSON 文字:- 不是合法 JSON →
(400, {"error": "invalid JSON"}) - 否则按顺序收集错误:
-
item不是非空字符串 →"item is required"
-qty不是 1 到 99 之间的 int(不能是布尔值)→"qty must be from 1 to 99"- 有错误 →
(400, {"errors": [...]}) - 合法 → 把
{"id": ..., "item": ..., "qty": ...}加进ORDERS(id 从 1 开始),返回(201, 订单)
- 不是合法 JSON →
本课术语
| 术语 | 中文 | 英文定义 |
|---|---|---|
| json module | json 模块 | json.loads turns JSON text into Python data; json.dumps does the reverse. |
| Validation | 校验 | Checking input before trusting it. |