人人都能学编程
English 开始学习
专项 · 后端第 2 课 / 共 6 课免费阅读

JSON API 与校验 · 后端开发

输入有问题时,API 应该怎么做?

课程目录

为什么需要它

任何东西都可能发到你的服务器上:拼写错误、缺字段、该填数字的地方填了文字,或者有人故意想把它搞坏。会检查输入、把每个问题说清楚的 API,用起来很舒服;崩溃或者悄悄存下垃圾数据的 API,会在离真正原因很远的地方引发 bug。

直觉理解

创建东西的请求,每次都走同样的步骤:

  1. 解析:用 json.loads 把 JSON 文字变成 Python 数据。不是合法 JSON → 400 {"error": "invalid JSON"}。
  2. 校验:检查每个字段:有没有?类型对不对?在不在范围内?把所有问题收进一个列表,让客户端一次改完 → 400 {"errors": [...]}。
  3. 执行:存起来。
  4. 响应: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
逐步执行
  1. L3JSON 对象变成字典,数组变成列表。
  2. L5Python 的 True 变成 JSON 的 true。{"ok": true, "count": 2}
  3. 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'})
逐步执行
  1. L5收集所有问题,而不只是第一个。
  2. L15第 1 步:解析。坏 JSON 在这里就以 400 结束。
  3. L18第 2 步:校验。
  4. L22第 3 步:保存,id 由服务器决定。
  5. 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 必须是数字。哪个响应合适?

  1. A400,带一条说明 qty 必须是数字的消息
  2. B500 Internal Server Error
  3. C200,照样存下来
查看答案

答案:A

这是客户端能改正的错误:4xx,带一条有用的消息。

2. 这段代码会输出什么?

import json
print(json.loads('[1, "2", true, null]'))
  1. A
    [1, '2', True, None]
  2. B
    [1, 2, True, None]
  3. C
    [1, '2', 'true', 'null']
查看答案

答案:A

JSON 的 true 和 null 变成 Python 的 True 和 None;"2" 还是字符串。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出三个请求

    准确写出它打印的内容。

  2. 改需求更严格的年龄检查

    validate 放过了不合理的年龄:-5、300、True 都能通过。修改它,要求 age 是 0 到 150 之间的 int(不能是布尔值),错误信息是 "age must be a whole number from 0 to 150"。保留对 name 的检查。

  3. 从零写创建订单

    写一个 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 modulejson 模块json.loads turns JSON text into Python data; json.dumps does the reverse.
Validation校验Checking input before trusting it.