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