JSON Schema

简介

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

https://github.com/xeipuuv/gojsonschema

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Golang高效实践之array、slice、map实践

    Golang的slice类型为连续同类型数据提供了一个方便并且高效的实现方式。slice的实现是基于array,slice和map一样是类似于指针语义,传递sl...

    用户2937493
  • 基于zookeeper或redis实现分布式锁

    在分布式系统中,分布式锁是为了解决多实例之间的同步问题。例如master选举,能够获取分布式锁的就是master,获取失败的就是slave。又或者能够获取锁的实...

    用户2937493
  • Golang微服务实践

    在之前的文章《漫谈微服务》我已经简单的介绍过微服务,微服务特性是轻量级跨平台和跨语言的服务,也列举了比较了集中微服务通信的手段的利弊,本文将通过RPC通信的方式...

    用户2937493
  • [市场活动] 2018,腾讯云助力公益和你在一起!

          申请用户必须是中国民政部门下合法注册成立的非盈利性质的、非党派性质的、非成员组织的、实行自主管理的民间志愿性的社会中介组织,其主要活动时致力于社会公...

    胡文翠
  • 爬虫非专业八级模拟考试

    最大的爬虫就是搜索引擎。Google作为世界上最大的搜索引擎,其爬虫流量也遥遥领先于其他各类搜索引擎爬虫,占所有爬虫流量的3.87%

    小小詹同学
  • mongodb 配置文件

    本文档是在mongodb为3.4下编写的,仅作为参考,详细内容请参考:https://docs.mongodb.com/manual/reference/con...

    拓荒者
  • 腾讯云助力公益和你在一起!

          申请用户必须是中国民政部门下合法注册成立的非盈利性质的、非党派性质的、非成员组织的、实行自主管理的民间志愿性的社会中介组织,其主要活动时致力于社会公...

    腾讯云-助力公益
  • 解决"QWindowsContext: OleInitialize() failed"运行时错误

    OleInitialize是一个Windows API函数。它的作用是在当前单元(apartment)初始化组件对象模型(COM)库,应用程序必须在调用COM...

    Qt君
  • 再说TCP神奇的40ms

    TCP是一个复杂的协议,每个机制在带来优势的同时也会引入其他的问题。 Nagel算法和delay ack机制是减少发送端和接收端包量的两个机制, 可以有效减少网...

    后端技术探索
  • 再说TCP神奇的40ms

    本文结合具体的 tcpdump 包,分析触发 delay ack 的场景,相关的内核参数, 以及规避的方案。

    安斌

扫码关注云+社区

领取腾讯云代金券