一. 背景
之前在做容器发布系统的时候,在部署时需要对提交的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
更多的例子可参考:
六、参考