JSON 作为通用的前后端交互,或者后台服务间通信的通用格式被大家广泛使用。我们肯定遇到过一些场景需要校验调用方传递过来的数据格式,比如一定要包含某些字段,某个字段一定要符合某种格式,比如定义了价格的字段,范围一定要在100~200之间,协议字段一定要是TCP或者UDP等枚举类型。你是否在你的用户代码里面自行实现这些判断逻辑呢?如果这样的规则越来越多是不是会显得代码很臃肿呢?这就是为什么要介绍我们今天的主角JSON Schema。JSON Schema定义了JSON格式的规范,各种语言都有开源的第三方JSON Schema校验库,例如Go语言的gojsonschema,这样我们就可以定义一份JSON Schema,然后系统的各个模块都可以复用这套JSON规范,不满足规则的数据JSON Schema会直接报错。
JSON Schema作为JSON的规范样式,自身也有一套key-value语法用于声明各种规则。
key | value | 备注 |
---|---|---|
$schema | http://json-schema.org/draft-04/schema# http://json-schema.org/draft-06/schema# http://json-schema.org/draft-07/schema# | 说明是哪个版本的JSON Schema,不同版本间不完全兼容 |
type | string、number、integer、boolean、object等 例如{"type":"integer"}说明该字段一定要是整形 | 说明字段的类型 |
pattern | { "type": "string", "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"} | 正则表达式 |
enum | { "type": "string", "enum": ["red", "amber", "green"]} | 枚举类型 |
properties | 说明该JSON对象有哪些属性/字段 "properties": { "street_address": { "type": "string" }, "city": { "type": "string" }, "state": { "type": "string" } }, | 属性字段 |
definitions | 通常搭配$ref一起说明使用 { "$ref": "#/definitions/address" } | 自定义字段 |
$ref | 通常用于复杂说明 | 引用字段 |
required | "required": ["street_address", "city", "state"] | 必须字段 |
oneof | { "oneOf": [ { "type": "number", "multipleOf": 5 }, { "type": "number", "multipleOf": 3 } ]} // 10 yes // 9 yes // 2 no | 满足其中一个 |
allof | { "allOf": [ { "type": "string" }, { "maxLength": 5 } ]} // "short" yes // "too long" no | 满足全部 |
multipleOf | { "type": "number", "multipleOf": 5 }, | 倍数 |
not | { "not": { "type": "string" } } // 42 yes //"hello" no | 取反 |
array | { "type": "array", "items": { "type": "number" }} | 数组 |
propertyNames | 正则字符串 | 定义key的正则规则 |
patternProperties | { "type":"object", "patternProperties":{ "^S_":{"type":"string"} }} | 同时限定key和value |
additionalProperties | boolean | 是否允许有格外的属性 |
dependencies | { "type":"object", "properties":{ "name":{"type":"string"}, "age":{"type":"number"} }, "dependencies":{ "gender":["age"] }} // { "gender":"male" } no// { "gender":"male", "age":18 } yes | 属性间依赖关系 |
uniqueItems | boolean | 数组元素是否唯一 |
minProperties/maxProperties | number | 最小/大属性个数 |
定义JSON Schema规则:
{
"$schema": "https://json-schema.org/draft-04/schema#",
"type": "object",
"properties":{
"schema_version": {
"type": "string"
},
"service": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false,
"required": [
"schema_version",
"service"
],
"definitions": {
"service": {
"type": "object",
"properties": {
"name": {
"$ref": "#/definitions/service_name"
},
"runtime": {
"$ref": "#/definitions/runtime_location"
},
"labels": {
"$ref": "#/definitions/service_labels"
},
"selector": {
"$ref": "#/definitions/service_selector"
},
"ports": {
"$ref": "#/definitions/service_ports"
}
},
"required": [
"name",
"runtime",
"labels",
"selector",
"ports"
]
},
"service_name": {
"type": "string",
"pattern": "^[a-z0-9-]+.[a-z0-9-]+$"
},
"service_labels": {
"type": "object",
"properties": {
"group": { "type": "string" },
"balance_strategy": { "enum": [ "source", "roundrobin", "leastconn" ]}
}
},
"service_ports": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string"
},
"domain_name": {
"type": "string"
},
"path": {
"type": "string"
},
"port": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": true
},
"protocol": {
"enum": [
"tcp",
"udp",
"http"
]
}
},
"required": [
"name",
"protocol",
"port"
]
}
},
"label_value": {
"type": "string",
"pattern": "(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])"
},
"version": {
"type": "string",
"pattern": ",^\\d+(\\.\\d+)+"
},
"service_selector": {
"type": "object",
"propertyNames": {
"pattern": "[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*"
},
"patternProperties": {
"[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*": {
"$ref":"#/definitions/label_value"
}
}
},
"runtime_location": {
"type": "string",
"pattern": "^[a-zA-Z0-9-_]+\\.[a-zA-Z0-9-_]+\\.[a-z0-9-_]+$"
}
}
}
保存为schema.json文件,后面代码会用到。
需要校验的JSON数据为:
{
"schema_version":"0.1.0",
"service":{
"name":"template-service",
"runtime":"30007.xx7.abc",
"labels":{
"group":"external",
"balance_strategy":"roundrobin"
},
"selector":{
"podname":"haha4-tester"
},
"ports":[
{
"name":"http8088",
"domain_name":"yyy.xxx.com",
"path":"/test/",
"port":8088,
"protocol":"http"
},
{
"name":"tcp-00",
"port":8800,
"protocol":"tcp"
}
]
}
}
保存为document.json文件。
下面我将用golang的第三方开源库gojsonschema校验上面的JSON数据是否符合我们定义的JSON Schema。
package main
import (
"fmt"
"github.com/xeipuuv/gojsonschema"
"io/ioutil"
)
func main() {
schemaContent, err := ioutil.ReadFile("schema.json")
if err != nil {
panic(err.Error())
}
jsonContent, err := ioutil.ReadFile("document.json")
if err != nil {
panic(err.Error())
}
loader1 := gojsonschema.NewStringLoader(string(schemaContent))
schema, err := gojsonschema.NewSchema(loader1)
if err != nil {
panic(err.Error())
}
documentLoader := gojsonschema.NewStringLoader(string(jsonContent))
result, err := schema.Validate(documentLoader)
if err != nil {
panic(err.Error())
}
if result.Valid() {
fmt.Printf("The document is valid\n")
} else {
fmt.Printf("The document is not valid. see errors :\n")
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
}
}
程序运行输出:
The document is valid
通过上面的介绍和代码示例,我们可以清楚的了解到了JSON Schema定义JSON数据规范的便利性和通用性,我们可以将JSON Schema应用到我们的代码用,减少JSON数据的校验冗余代码。
https://json-schema.org/understanding-json-schema/reference/index.html
https://json-schema.org/learn/getting-started-step-by-step.html