最近我在为 openEuler 社区添加一个 FAQ 模块, 这一系列正是我在这一过程中的总结
全部内容: 如何编写一个 Python Web 应用(零)
这是我发现的一个强大的 json 数据校验工具, 不止可以用在 flask app 中 json 数据的校验, 在任何场景 json 数据的校验都非常有力
一般的, 数据校验包含这样几个层次:
- 格式校验: 是否符合 json 语法
- 属性校验: 对于 json 表示的对象 (object) 是否包含指定的属性, 是否包含了其他不需要的数据, 以及每种属性是否是规定的类型
- 值校验: 对数据取值进行校验. 例如规定字符串的长度范围, 数字属性的取值范围, 集合属性的元素个数和元素的数据类型
- 逻辑校验: 这就和业务逻辑相关了. 比如传入的 id 指向的用户是否有操作的权限. 传入的数据是否与数据库中已有的数据冲突等. 这一块就很难使用框架实现了.
首先, 什么是 JSON Schema? 可以参考下面的资料:
jsonschema 及其衍生的工具生态除了提供上述功能外, 还其他提升易用性的工具:
- jsonschema (python module): 为数据校验提供了一个 SDK, 提供了校验接口, 和详尽的错误提示功能
- json schema Tool: 一个在线的 json schema 生成与图形化编辑工具, 帮助你写出符合语法的 json schema 规约文件. 网址
下面是一个涵盖大部分用例的 jsonschema python module 使用案例(参考)
from jsonschema import validate, ValidationError # 导入参数的包
@app.route('/login4', methods=['POST'])
def login4():
body = request.get_json()
try:
validate(
body,
{
"$schema": "http://json-schema.org/learn/getting-started-step-by-step",
# 描述对应的JSON元素,title相对来说,更加简洁
"title": "book info",
# 描述对应的JSON元素,description更加倾向于详细描述相关信息
"description": "some information about book",
# 该关键字用于限定待校验JSON元素所属的数据类型,取值可为:object,array,integer,number,string,boolean,null
"type": "object",
# 用于指定JSON对象中的各种不同key应该满足的校验逻辑,
# 如果待校验JSON对象中所有值都能够通过该关键字值中定义的对应key的校验逻辑,每个key对应的值,都是一个JSON Schema,则待校验JSON对象通过校验。
"properties": {
"id": {
"description": "The unique identifier for a book",
"type": "integer",
"minimum": 1
},
"name": {
"description": "book name",
"type": "string",
"minLength": 3,
"maxLength": 30
},
"tips": {
"anyOf": [ # 满足其中一个类型 就行
{"type": "string", "minLength": 10, "maxLength": 60},
{"type": "number", "minimum": 5.0}
]
},
"price": {
"description": "book price",
"type": "number",
# 能被0.5整除
"multipleOf": 0.5,
# 这里取等,5.0=<price<=99999.0
"minimum": 5.0,
"maximum": 99999.0,
# 若使用下面这两个关键字则 5.0<price<99999.0
# "exclusiveMinimum": 5.0,
# "exclusiveMaximum": 99999.0
},
"tags": {
"type": "array",
"items": [
{
"type": "string",
"minLength": 2,
"maxLength": 8
},
{
"type": "number",
"minimum": 1.0
}
],
# 待校验JSON数组第一个元素是string类型,且可接受的最短长度为5个字符,第二个元素是number类型,且可接受的最小值为10
# 剩余的其他元素是string类型,且可接受的最短长度为2。
"additonalItems": {
"type": "string",
"miniLength": 2
},
# 至少一个
"miniItems": 1,
# 最多5个
"maxItems": 5,
# 值为true时,所有元素都具有唯一性时,才能通过校验。
"uniqueItems": True
},
"date": {
"description": "书籍出版日期",
"type": "string",
# 可以是以下取值:date、date-time(时间格式)、email(邮件格式)、hostname(网站地址格式)、ipv4、ipv6、uri等。
# 使用format关键字时,在实例化validator时必须给它传format_checker参数,值如:draft7_format_checker, 网址:
# https://python-jsonschema.readthedocs.io/en/latest/validate/#jsonschema.Draft7Validator
"format": "date",
},
"bookcoding": {
"description": "书籍编码",
"type": "string",
# 符合该关键字指定的正则表达式,才算通过校验。
"pattern": "^[A-Z]+[a-zA-Z0-9]{12}$"
},
"other": {
"description": "其他信息",
"type": "object",
"properties": {
"info1": {
"type": "string"
},
"info2": {
"type": "string"
}
}
}
},
# 指定了待校验JSON对象可以接受的最少 一级key 的个数
"minProperties": 3,
# 指定了待校验JSON对象可以接受的最多 一级key 的个数。
"maxProperties": 7,
# patternProperties对象的每一个一级key都是一个正则表达式,value都是一个JSON Schema。
# 只有待校验JSON对象中的一级key,通过与之匹配的patternProperties中的一级正则表达式,对应的JSON Schema的校验,才算通过校验。
# 下面的JSON Schema表示, 所有以a开头的一级key的value都必须是number,
"patternProperties": {
"^a": {
"type": "number"
},
},
# 如果待校验JSON对象中存在,既没有在properties中被定义,又没有在patternProperties中被定义,那么这些一级key必须通过additionalProperties的校验。
"additionalProperties": {
"desc": {
"type": "string",
"minLength": 1
},
},
# 该关键字限制了JSON对象中必须包含哪些一级key。
# 如果一个JSON对象中含有required关键字所指定的所有一级key,则该JSON对象能够通过校验。
"required": ["id", "name", "info", "price"]
})
except ValidationError as e:
msg = "json数据不符合schema规定:\n出错字段:{}\n提示信息:{}".format(" --> ".join([i for i in e.path]), e.message)
print(msg)
return jsonify(status=500, msg=msg)
print(body)
title = body.get('title')
return '1'
再加一个使用 枚举 的例子. 枚举指定了属性可以取哪些值.
参考: enum 的文档
def check_request(user_id, req_body):
if request.is_json:
handle_request_schema = {
"title": "handle requests",
"type": "object",
"properties": {
"id": {"type": "string", "maxLength": 20},
"tags": {
"type": "array",
"items": {"enum": [tag.tag_name for tag in ETag.query.all()]},
"uniqueItems": True
},
"self_answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string", "maxLength": 20},
"type": {"enum": [elem.type_name for elem in CAnswerType.query.all()]},
"level": {"enum": [elem.level for elem in CAnswerLevel.query.all()]}
},
"required": ["id", "allowed", "comment", "author_id",
"type", "content", "summary", "level"]
}
},
"adjusted_answers": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string", "maxLength": 20},
"level": {
"enum": [level.level for level in CAnswerLevel.query.all()]}
},
},
}
}
}
try:
validate(req_body, handle_request_schema)
except ValidationError as e:
msg = "json数据不符合schema规定:\n出错字段:{}\n提示信息:{}".format(".".join([str(i) for i in e.path]), e.message)
return msg, 500
else:
return "请求体必须是JSON格式", 500
return "", 200
值得一提的是, 在上面这个例子中, 枚举取值是在运行时动态加载的, 这给程序的编写和维护提供了很大的便利性
不仅是枚举, 实际上字段的长度也可以通过 SQLAlchemy 动态获取:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。