【Python】Json Schema的使用【原创】

  • Post author:
  • Post category:python




一. 背景

之前在做容器发布系统的时候,在部署时需要对提交的deployment.yaml进行校验,而由于deployment.yaml虽然可以解析为Json,但在面对很多的参数校验时候进行如下的校验就显得力不从心了:

aaa = request.get('aaa', '')
if not aaa:
    return False, 'aaa不能为空'

bbb = request.get('bbb', 0)
if not bbb:
    return False, 'bbb不能为空'

if bbb > 10 or bbb < 1:
    return False, 'bbb必须在1-10之间'

这个时候就在找能够处理大量参数校验的方法或者是可用的库,而Json Schema就刚好比较符合。



二、概述

Json Schema是基于Json格式、用于定义Json数据结构以及校验Json数据内容的工具,

官方文档

提供了比较多的例子,比如Json Schema提供了anyOf、allOf、oneOf、not等组合规则来进行严格的校验规则。

首先需要来了解一下Json的数据类型:

# 对象,object
{"key1": "value1", "key2": 22}

# 数组/列表,array
["first", "second", "third"]

# 数字,number
42
3.1415

# 字符串,string
"Hello World"

# 布尔值,boolean
true
false

# null值,null
null

比如一个json格式的例子:

{
 "fruits": ["apple", "orange", "pear"],
 "vegetables": [
   {
     "veggieName": "potato",
     "veggieLike": true
   },
   {
     "veggieName": "broccoli",
     "veggieLike": false
   }
 ]
}



三、介绍

JSON Schema定义了JSON格式的规范,各种语言都有开源的第三方JSON Schema校验库,例如Go语言的gojsonschema,Python的jsonschema等。

这样我们就可以定义一份JSON Schema,然后系统的各个模块都可以复用这套JSON规范,不满足规则的数据JSON Schema会直接报错。

目前最新的Json Schema版本为draft 8,发布于2019年9月。

JSON Schema作为JSON的规范样式,自身也有一套key-value语法用于声明各种规则。

Json Schema中常见关键字有:

关键字 描述
$schema 表示该JSON Schema文件遵循的规范和使用的版本,可以不包含,注意:不同版本间不完全兼容
$id 当前schema的唯一id标识,一般指向一个自主域名。方便后续引用,可以不包含
title 为该JSON Schema文件提供一个标题,可不包含
description 关于该JSON Schema文件的描述信息,可不包含
type 表示待校验元素的类型,例如,最外层的type表示待校验的是一个JSON对象(object)

内层type表示待校验的元素类型有:整数(integer),字符串(string),数字(number),布尔值(boolean)、对象(object)、数组(array)、null
properties 说明该JSON对象有哪些属性/字段
enum 枚举类型,比如只能在这几种:[“red”, “amber”, “green”]
required 必须存在的字段
minimum 用于约束取值范围,表示取值范围应该大于或等于minimum
exclusiveMinimum 如果minimum和exclusiveMinimum同时存在,且exclusiveMinimum的值为true,则表示取值范围只能大于minimum
maximum 用于约束取值范围,表示取值范围应该小于或等于maximum
exclusiveMaximum 如果maximum和exclusiveMaximum同时存在,且exclusiveMaximum的值为true,则表示取值范围只能小于maximum
multipleOf 用于约束取值,表示取值必须能够被multipleOf所指定的值整除
maxLength/minLength 字符串类型数据的最大/最小长度
dependencies 字段的依赖关系,比如:

{


“type”:“object”,

“properties”:{


“name”:{“type”:“string”},

“age”:{“type”:“number”}

},

“dependencies”:{ “gender”:[“age”] } }

表示:gender是依赖于age存在的,age字段不存在但存在gender字段是会报错的
pattern 使用正则表达式约束字符串类型数据,比如:^(\([0-9]{3}\))?[0-9]{3}-[0-9]{4}$
multipleOf 倍数,比如{ “type”: “number”, “multipleOf”: 5 }表示:字段必须是5的倍数
not 取反,比如:{ “not”: { “type”: “string” } },非字符串
array 数组
oneof 满足其中一个,比如:{


“oneOf”: [

{ “type”: “number”, “multipleOf”: 5 },

{ “type”: “number”, “multipleOf”: 3 }

]

}

表示要么是5的倍数,要么是3的倍数
allof 都要满足,用法类似上面的oneof
uniqueItems 在数组里面的,布尔值,表示数组元素是否唯一
minProperties/maxProperties 在object里面的,表示object里面的最小/最大的字段个数
maxItems/minItems 表示数组里面的元素最大/最小个数
minLength/maxLength 字符串类型数据的最小/最大长度



官网

一个示例来进行介绍:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/product.schema.json",
  "title": "Product",
  "description": "A product from Acme's catalog",
  "type": "object",
  "properties": {
    "productId": {
      "description": "The unique identifier for a product",
      "type": "integer"
    },
    "productName": {
      "description": "Name of the product",
      "type": "string"
    },
    "price": {
      "description": "The price of the product",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "tags": {
      "description": "Tags for the product",
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true
    },
    "dimensions": {
      "type": "object",
      "properties": {
        "length": {
          "type": "number"
        },
        "width": {
          "type": "number"
        },
        "height": {
          "type": "number"
        }
      },
      "required": [ "length", "width", "height" ]
    }
  },
  "required": [ "productId", "productName", "price" ]
}

进行具体的分析说明:

# 说明当前使用的schema版本,可以不包含
"$schema": "http://json-schema.org/draft-07/schema#",

# 当前schema的唯一id标识,一般指向一个自主域名。方便后续引用,可以不包含
"$id": "http://example.com/product.schema.json",

# 当前schema的标题,简要描述信息,可不包含
"title": "Product",

# 详细描述信息,可不包含
"description": "A product from Acme's catalog",

# 约束对象是object,也就是在 properties{ } 中的数据
"type": "object",

# object中具体属性的约束,description是描述信息,不产生具体约束。type约束productid属性类型为整型
"properties": {
    "productId": {
      "description": "The unique identifier for a product",
      "type": "integer"
    },

# 约束price属性类型为数字型,可以是整型或浮点型。exclusiveMinimum约束该数字>0(不包含0)
    "price": {
      "description": "The price of the product",
      "type": "number",
      "exclusiveMinimum": 0
    },

# 约束tag属性是array数组。items是数组项约束(即数组里面的每个元素),这里约束数组项均为字符型,minItems数组至少包含1项,uniqueItems约束数组中每项不得重复
    "tags": {
      "description": "Tags for the product",
      "type": "array",
      "items": {
        "type": "string"
      },
      "minItems": 1,
      "uniqueItems": true
    },
  
# 约束dimensions嵌套对象,其中length,width,height均为数字类型,且这三个字段在dimensions对象中必须包含
 "dimensions": {
      "type": "object",
      "properties": {
        "length": {
          "type": "number"
        },
        "width": {
          "type": "number"
        },
        "height": {
          "type": "number"
        }
      },
  "required": [ "length", "width", "height" ]
    }

# 当前数据对象必须包含productId,productName,price三个字段
 "required": [ "productId", "productName", "price" ]
}



四、Python中使用

注意:不止Python语言,很多语言都有开源的第三方JSON Schema校验库,例如Go语言的gojsonschema,Python的jsonschema等。

以Python为例:

A. 先安装Json Schema依赖包

pip install jsonschema

B. 编写schema内容(一般是会将该内容写到json文件里面去然后读取)

schema = {
    "title": "test demo",
    "description": "validate result information",
    "type": "object",
    "properties": {
        "code": {
            "description": "error code",
            "type": "integer"
        },
        "msg": {
            "description": "error msg ",
            "type": "string"
        },
        "token":
        {
            "description": "login success return token",
            "maxLength": 40,
            "pattern": "^[a-f0-9]{40}$",  # 正则校验a-f0-9的16进制,总长度40
            "type": "string"
        }
    },
    "required": [
        "code", "msg", "token"
    ]
}

表示:

  • 包含字段:code、msg、token
  • code必须是整数
  • msg必须是字符串
  • token必须是字符串,最大长度为40,且只有a-f0-9这几个字符

C. 进行校验

from jsonschema import validate, draft7_format_checker
from jsonschema.exceptions import SchemaError, ValidationError

result = {
    "code": 0,
    "msg": "login success!",
    "token": "000038efc7edc7438d781b0775eeaa009cb64865",
    "username": "test"
}

try:
    validate(instance=result, schema=schema, format_checker=draft7_format_checker)
    except SchemaError as e:
        return 1, "验证模式schema出错,出错位置:{},提示信息:{}".format(" --> ".join([i for i in e.path if i]), e.message)
    except ValidationError as e:
        return 1, "不符合schema规定,出错字段:{},提示信息:{}".format(" --> ".join([i for i in e.path if i]), e.message)
    else:
        return 0, 'success'

validate 校验成功时候,不会有报错

  • JSON 数据校验失败,抛出 jsonschema.exceptions.ValidationError 异常
  • schema 模式本身有问题,抛出 jsonschema.exceptions.SchemaError 异常



五、例子

校验kubnetets的deployment:

{
    "title": "k8s deployment schema",
    "description": "check k8s deployment",
    "type": "object",
    "properties": {
        "apiVersion": {
            "type": "string"
        },
        "kind": {
            "type": "string",
            "enum": ["Deployment"]
        },
        "metadata": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string"
                },
                "labels": {
                    "type": "object"
                }
            },
            "required": ["name", "labels"]
        },
        "spec": {
            "type": "object",
            "properties": {
                "replicas": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 100
                },
                "selector": {
                    "type": "object"
                },
                "template": {
                    "type": "object",
                    "properties": {
                        "metadata": {
                            "type": "object"
                        },
                        "spec": {
                            "type": "object",
                            "properties": {
                                "containers": {
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "name": {
                                                "type": "string"
                                            },
                                            "livenessProbe": {
                                                "type": "object"
                                            }
                                        },
                                        "required": ["name", "livenessProbe"]
                                    }
                                },
                                "nodeSelector": {
                                    "type": "object"
                                },
                                "volumes": {
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "hostPath": {
                                                "type": "object"
                                            },
                                            "name": {
                                                "type": "string"
                                            }
                                        },
                                        "required": ["hostPath", "name"]
                                    }
                                }
                            },
                            "required": ["containers", "nodeSelector", "volumes"]
                        }
                    },
                    "required": ["metadata", "spec"]
                }
            },
            "required": ["replicas", "selector", "template"]
        }
    },
    "required": ["apiVersion", "kind", "metadata", "spec"]
}

即:

apiVersion、kind、metadata、spec必填

其中:

  • apiVersion:字符串
  • kind:必须为Deployment
  • metadata:object
  • spec:object

metadata下name、labels必填

其中:

  • name:字符串
  • labels:object

spec下的replicas、selector、template必填

其中:

  • replicas:数字,且1-100之间
  • selector:object
  • template:object

spec 下的 template 下的metadata、spec必填

其中:

  • metadata:object
  • spec:object

spec 下的 template 下的 spec 下的containers、nodeSelector、volumes必填

其中:

  • containers:数组
  • nodeSelector:object
  • volumes:数组

spec 下的 template 下的 spec 下的 containers 下的每个元素,为object,必须包含name、livenessProbe

其中:

  • name:字符串
  • livenessProbe:object

spec 下的 template 下的 spec 下的 volumes 下的每个元素,为object,必须包含name、hostPath

其中:

  • name:字符串
  • hostPath:object

更多的例子可参考:



六、参考



版权声明:本文为jiandanokok原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。