JSON
存储数据时、数据传输时、组件交互时以及数据验证的时候,经常提到JSON,在不同平台、组件及场景下的应用,已经自证了它作为无关平台标记语言的易用性及通用性。使用JSON同时,结合JSON Schema能够完成数据定义、数据传输以及数据验证的完整过程。
起源
谈到JSON,就会想到XML,因为都是优秀的标记描述语言,常用来对比使用,就像同功能的多种框架有的在扩展方面极佳,有的则配置极简上手简单,JSON和XML也一样,各有优缺点。
XML的出现和HTML息息相关,20世纪90年代HTML「Hypertext Markup Language(超文本标记语言)」出现,用来描述网页文档,加速了早期Web的发展,随着Web普及,网站信息体量急速增长,HTML可读性差、解析时间长以及数据描述性差等弊端逐渐显露。
在该背景下,XML「Extensible Markup Language (可扩展标记语言)」标记语言,通过系列标签,为Web应用提供了一套简单、高效且标准的数据描述方法,它作为描述数据和进行数据交换的有效手段,和HTML非竞争关系,而是互为补充。HTML用于网页显示,XML在于数据结构化描述以及数据的存储,有独立于平台、软件、语言的属性。
XML标记语言是基于DOM树实现的,而DOM树在不同浏览器实现不同,会有兼容性问题,这就导致XML在跨浏览器兼容性不太好,因此急需一种通用型数据结构,该数据结构能够在所有浏览器间自由穿行,也就是说能够被JavaScript引擎所识别,即JSON「JavaScript Object Notation(JavaScript 对象表示法)」出现,结构简单、没有复杂规则并且具备自我描述性。
JSON的出现使数据交换变得简单,基本不需要理解描述标签,且可以直接被JavaScript引擎所理解,当不同平台进行数据交互的时候,通用的数据结构描述语言必不可少,可以简化数据共享、简化数据传输、简化平台变更。。
是什么
了解JSON的诞生背景,也就知道了它是为了解决哪些问题而出现的,定义:
JSON(JavaScript Object Notation, JS对象简谱)是一种轻量级的数据交换格式。
它基于 ECMAScript(European Computer Manufacturers Association, 欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。
简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JSON是一种数据交换格式,即在不同组件、平台以及设备间数据交换的协议,也是存储数据的格式。从背景和定义看出,JSON的诞生和Web、JavaScript引擎发展是离不开的,但是它的应用不限于此,很多语言本身或者通过三方来支持JSON格式。
JSON是一种数据描述语言,通过规定格式,能够存储和表示所需数据,这种通用的标准减少了数据交换因为数据规范不同而带来的理解成本。
应用场景
JSON作为一种规范,或者说是格式,它是开放性的,很难说它适合什么场景,有数据存在的地方,都可以考虑JSON描述语言。
-
数据共享
系统间进行数据交换,依托数据描述能力定义数据格式,是JSON诞生的重要原因,比如:web和服务端进行数据请求和返回,以SpringMVC框架为例,使用Jsckson工具进行JSON数据的解析。通常在描述接口参数、接口返回、网关API信息都会使用JSON来进行数据格式的描述。 -
数据传输
数据是以流的方式进行网络传输,当发送端把数据转化成流发送,那么接收端需要理解数据的格式,这样才能正确解析流,这种数据传输也就是正反序列化的过程。序列化的形式有很多,JSON也是其中一种,包括jdk、Hession、Protocolbuffer等等,JSON已经是序列化形式的重要选项。 -
数据存储
目前有很多存储介质支持JSON格式数据的存储解析,包括Mysql从5.7版本支持原生的JSON格式,MongoDB作为文档类型数据库,以JSON格式存储和使用。
格式
JSON以简洁著称,定义格式简单,以“键/值”结构组成,值的形式有多种,以描述中国城市为例,看下JSON和XML的描述示例:
XML的示例:
<?xml version="1.0" encoding="utf-8"?>
<country>
<name>中国</name>
<province>
<name>黑龙江</name>
<cities>
<city>哈尔滨</city>
<city>大庆</city>
</cities>
</province>
<province>
<name>广东</name>
<cities>
<city>广州</city>
<city>深圳</city>
<city>珠海</city>
</cities>
</province>
<province>
<name>台湾</name>
<cities>
<city>台北</city>
<city>高雄</city>
</cities>
</province>
<province>
<name>新疆</name>
<cities>
<city>乌鲁木齐</city>
</cities>
</province>
</country>
JSON的示例:
{
"name": "中国",
"province": [{
"name": "黑龙江",
"cities": {
"city": ["哈尔滨", "大庆"]
}
}, {
"name": "广东",
"cities": {
"city": ["广州", "深圳", "珠海"]
}
}, {
"name": "台湾",
"cities": {
"city": ["台北", "高雄"]
}
}, {
"name": "新疆",
"cities": {
"city": ["乌鲁木齐"]
}
}]
}
二者层次结构和语法格式都很清晰 ,其中XML由众多标签以及尖括号组成,因此从占用空间看XML是相对比较大的,JSON则以多种括号和几种常见标识组成,6个构造符、字符串、数字和3个字面名组成:
-
构造符:
左方括号:[
右方括号:]
左花括号:{
右花括号:}
冒号::
逗号:, -
字面名
否:false
空:null
是:true
[ ] 用于描述数组,{ } 用于描述对象,key值用字符串表示,值可以是数组、对象、数字、字符串以及三个字面值。
JSON的key是带引号的,这里其实是有个原因的:
最初的 JSON 信息实际上与 JavaScript 解释器发生了冲突。
JavaScript 保留了大量的关键字(ECMAScript 6 版本就有 64 个保留字),Crockford 和 Morningstar 无意中在其 JSON 中使用了一个保留字:do。
因为 JavaScript 使用的保留字太多了,所以Crockford决定:既然不可避免的要使用到这些保留字,那就要求所有的 JSON 键名都加上引号。被引起来的键名会被 JavaScript 解释器识别成字符串。这就为什么今天 JSON 键名都要用引号引起来的原因。
JSON Schema
了解了JSON出现的背景,知道了JSON的基础结构和描述形式,那么当和三方进行JSON交互,如何描述你所需要的JSON格式?给出一段JSON数据时,如何确保这段JSON数据是所需JSON格式?如何对这段JSON进行信息的验证?JSON Schema就是为了解决这些问题出现。
是什么
JSON Schema,直译JSON 规则。好比一个POJO是对一个实体的抽象,是描述一个实体的Schema;数据库中的表,是对数据范式的抽象,也是一种Schema;轿车的设计图于轿车而言,也是一种规范。JSON Schema则是对JSON的抽象和描述,定义了JSON的格式,能够对JSON实例数据进行验证:
JSON Schema是描述你的JSON数据格式;JSON模式(应用程序/模式+ JSON)有多种用途,其中之一就是实例验证。验证过程可以是交互式或非交互式的。例如,应用程序可以使用JSON模式来构建用户界面使互动的内容生成除了用户输入检查或验证各种来源获取的数据。
JSON Schema用特定的关键属性对JSON进行描述,能够清晰的知道JSON的组织形式、字段预期等信息。
应用场景
上面提到了很多关于使用JSON时候遇到的问题,这些问题就是JSON Schema出现的原因,也是其应用的场景。
-
实例验证
JSON的实例验证,当通过交互式或非交互式的方式进行数据输入时,如果使用JSON格式的数据传输,可以通过JSON Schema对输入的JSON数据进行数据格式、数据类型等进行检查,以保证进来的数据准确性。 -
JSON解释
JSON Schema对JSON的格式和规则进行了描述,对象、属性、类型、格式等必要信息,通过JSON Schema,能清晰的知道该JSON数据的所有信息。
格式
作为描述JSON的规则,它本身也是用JSON格式来表示的,用一些特定的关键词描述特定的含义,比如“type”用来标识类型,“$schema”标识模式,在不同版本协议下,关键字会有变化,就像不同的http协议所支持的功能也不同,目前存在很多规范版本:
Draft 4 的标识符是http://json-schema.org/draft-04/schema#
…
Draft 6 的标识符是http://json-schema.org/draft-06/schema#
Draft 7 的标识符是http://json-schema.org/draft-07/schema#
下面看下它是如何用JSON来描述JSON的:
- 可以用 true、{},来表示任何有效的JSON数据
- 基础关键字如下:
关键字 | 描述 |
---|---|
$schema | 声明此json片段属于JSON Schema,并遵循所声明的JSON Schema版本规范 |
$id | 为JSON Schema声明一个统一资源标识符,使解析$ref时能够引用片段 |
title | 为JSON Schema文件提供标题 |
description | 为JSON Schema文件提供描述信息 |
definitions | 声明子schema,使解析$ref能够引用片段 |
$ref | 引用JSON Schema片段 |
required | 定义对象类型properties所声明的字段是否必须,值必须是数组,数组中的元素必须是字符串类型且唯一 |
type | 定义元素的类型 |
properties | 定义对象类型里的属性(键值对),每个字段的值都是一个有效的schema片段,用来限制每个字段的格式 |
minimum | 约束取值范围,标识取值范围应该大于或等于minimum |
exclusiveMinimum | 假若minimum或exclusiveMinimum同时存在,且exclusiveMinimum为true,则取值范围大于minimum |
maximum | 约束取值范围,标识取值范围应该小于或等于maximum |
exclusiveMaximum | 假若maximum或exclusiveMaximum同时存在,且exclusiveMaximum为true,则取值范围小于maximum |
minLength | 字符串类型数据的最小长度 |
maxLength | 字符串类型数据的最大长度 |
pattern | 使用正则表达式约束字符串类型数据 |
items | 用来定义数组类型的子元素,值必须为数组,且是一个有效的schema片段 |
minItems | 定义数组类型大小的最小长度 |
maxItems | 定义数组类型大小的最大长度 |
uniqueItems | 定义数组类型子元素是否必须唯一 |
enum | 用来限制值的范围,值必须在enum所指定的集合里面 |
- 一些高级用法
- 组合模式「allOf、anyOf、oneOf,not」:布尔代数概念如 AND、OR、XOR 和 NOT,表达无法用标准 JSON Schema 关键字表达的复杂约束。
- 必要依赖「dependentRequired」:如果一个对象存在某个特定的属性,则另一个属性也必须存在。
- 模式依赖「dependenciesSchemas」:关键字要求当给定的属性存在时,有条件地应用子模式。
- 条件语句「if / else / then」:Draft7中,允许基于另一种模式的结果来应用子模式。
{
"type": "object",
"properties": {
"street_address": {
"type": "string"
},
"country": {
"default": "United States of America",
"enum": ["United States of America", "Canada"]
}
},
"if": {
"properties": { "country": { "const": "United States of America" } }
},
"then": {
"properties": { "postal_code": { "pattern": "[0-9]{5}(-[0-9]{4})?" } }
},
"else": {
"properties": { "postal_code": { "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" } }
}
}
更多的关键字和使用方式可以参考JSON Schema的官网。
生成与使用
如果对Schema格式了解,那么定义正确的Schema很简单,可以通过在线网站生成,也可以通过一些集成工具生成,比如引入工具jar,通过POJO转化JSON Schema,以工具xxxx为例:
- 根据POJO生成JSON Schema
\\生成JSON Schema
ObjectMapper mapper = new ObjectMapper();
SchemaFactoryWrapper visitor = new SchemaFactoryWrapper();
mapper.acceptJsonFormatVisitor(EntertainmentTargetVo.class, visitor);
JsonSchema jsonSchema = visitor.finalSchema();
String schemaString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonSchema);
fasterxml提供了生成JSON Schema的工具包,jackson-module-jsonSchema,但是该工具包开发机遇V3,由于JSON Schema Draft4以上版本的不兼容,导致该工具包无法向上升级,因此在其他模块项目重新支持其他版本,即mbknor-jackson-jsonschema_2.12 工具包。
- JSON Schema实例验证
\\JSON Schema验证JSON数据
Schema schema = SchemaLoader.load(rawSchema);
try {
schema.validate(jsonObject);
} catch (ValidationException e) {
System.out.println(JSON.toJSONString(e.getAllMessages()));
throw e;
}
输入一段JSON数据,通过JSON Schema对该JSON实例进行合法性校验,示例使用工具 everit-json-schema,还有很多相关验证工具,选型需要注意所支持的JSON Schema协议版本。
参考文章
[1] : https://json-schema.org/
[2] : https://www.json.org/json-zh.html
[3] : https://www.runoob.com/json/json-intro.html