如何编写一个 Python Web 应用 (三):JSON schema 与复杂 json 请求体的校验

这是我发现的一个强大的 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= ".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 动态获取:

方法: how-to-get-sqlalchemy-length-of-a-string-column

你可能感兴趣的:(如何编写一个 Python Web 应用 (三):JSON schema 与复杂 json 请求体的校验)