jsonschema 模式校验
https://python-jsonschema.readthedocs.io/en/latest/
pip install jsonschema -i https://mirrors.aliyun.com/pypi/simple/
1. 什么是模式
https://json-schema.org/understanding-json-schema/about.html
如果你曾经使用过 XML Schema,RelaxNG 或 ASN.1,那么你可能已经知道什么是模式,并且可以高兴地跳到下一节。如果你听起来像个傻瓜,那你来对地方了。要定义什么是 JSON 模式,我们可能应该首先定义什么是 JSON。
JSON 代表JavaScript 对象表示法
,这是一种简单的数据交换格式。它最初是作为万维网的一种表示法。由于大多数网络浏览器中都存在 JavaScript,并且 JSON 基于 JavaScript,因此很容易在其中进行支持。但是,它已经被证明足够有用和足够简单,现在可以在许多不涉及网络冲浪的情况下使用它。
JSON 本质上是基于以下数据结构构建的:
-
object:
{ "key1": "value1", "key2": "value2" }
-
array:
[ "first", "second", "third" ]
-
number:
42 3.1415926
-
string:
"This is a string"
-
boolean:
true false
-
null:
null
这些类型在大多数编程语言中都有类似物,尽管它们的名称可能不同。
下表从 JavaScript 类型的名称映射到 Python 中的类似类型:
JavaScript | Python |
---|---|
string | string |
number | int/float |
object | dict |
array | list |
boolean | bool |
null | None |
JavaScript 没有针对整数和浮点数的单独类型。
使用这些简单的数据类型,可以表示所有类型的结构化数据。但是,随着灵活性的提高,责任也就增加了,因为同一概念可以用多种方式表示。例如,你可以想象以不同的方式用 JSON 表示有关一个人的信息:
{
"姓名": "张三",
"出生日期": "1990年01月01日",
"地址": "中国广东省深圳市宝安区"
}
{
"姓": "张",
"名": "三",
"出生日期": "1990-01-01",
"地址": {
"国家": "中国",
"省": "广东省",
"市": "深圳市",
"区": "宝安区"
}
}
两种陈述都同样有效,尽管其中一种显然比另一种更为正式。记录的设计将在很大程度上取决于其在应用程序中的预期用途,因此这里没有正确或错误的答案。但是,当应用程序说“给我一个人的 JSON 记录”时,准确知道该记录的组织方式非常重要。
例如,我们需要知道需要哪些字段以及如何表示值。
这就是 JSON Schema 的来源。以下 JSON Schema 片段描述了上面第二个示例的结构。现在不必担心太多细节。在随后将对它们进行说明。
{
"type": "object",
"properties": {
"姓": { "type": "string" },
"名": { "type": "string" },
"出生日期": { "type": "string", "format": "date" },
"地址": {
"type": "object",
"properties": {
"国家": { "type": "string" },
"省": { "type": "string" },
"市": { "type": "string" },
"区": { "type" : "string" }
}
}
}
}
通过针对该模式“验证”第一个示例,你可以看到它失败了。但是,第二个示例通过了:
你可能已经注意到 JSON Schema 本身是用 JSON 编写的。它是数据本身,而不是计算机程序。它只是“描述其他数据的结构”的声明性格式。
这既是它的强项,也是它的弱项(与其他类似的模式语言共享)。简明扼要地描述数据的表面结构,并根据它自动进行数据验证很容易。
但是,由于 JSON 模式不能包含任意代码,因此无法表达的数据元素之间的关系受到某些限制。
因此,任何用于足够复杂的数据格式的“验证工具”都可能具有两个验证阶段:一个在模式(或结构)级别,另一个在语义级别。后者检查可能需要使用更通用的编程语言来实现。
2. 总纲
https://www.jsonschema.net/home
3. JSON 模式参考
https://json-schema.org/understanding-json-schema/reference/index.html
3.1. 特定类型的关键字
- 字符串:string
- 数值类型:integer、number、Multiples、Range
- 对象:object
- 数组:array
- 布尔值:boolean
- 空:null
{ "type": "number" }
// 成功:10、20.5
// 失败:"10"
{ "type": ["number", "string"] }
// 成功:10、20.5、"10"、"你好"
// 失败:[1, 2, 3]
type 关键字可以是一个字符串或数组:
- 如果是字符串,则为上述基本类型之一的名称。
- 如果是数组,则必须是字符串数组,其中每个字符串是一种基本类型的名称,并且每个元素都是唯一的。在这种情况下,如果 JSON 代码段与任何给定类型匹配,则该代码段有效。
3.2. 正则表达式
该模式和模式属性关键字使用正则表达式来表示约束。使用的正则表达式语法来自 JavaScript(尤其是 ECMA 262)。
但是,不完全支持完整的语法,因此建议你坚持下面描述的该语法的子集。
- 单个 unicode 字符(下面的特殊字符除外)与自己匹配。
.
:匹配除换行符以外的任何字符。(请注意,组成换行符的内容在某种程度上取决于你的平台和语言环境,但实际上这并不重要)。^
:仅在字符串开头匹配。$
:仅在字符串末尾匹配。(...)
:将一系列正则表达式分组为一个正则表达式。|
:匹配|符号之前或之后的正则表达式。[abc]
:匹配方括号内的任何字符。[a-z]
:匹配字符范围。[^abc]
:匹配未列出的任何字符。[^a-z]
:匹配范围之外的任何字符。+
:匹配前一个正则表达式的一个或多个重复。*
:匹配零个或多个前面的正则表达式重复。?
:匹配前一个正则表达式的零个或一个重复。+?
,*?
,??
:的*
,+
和?
预选赛都是贪婪的;它们匹配尽可能多的文本。有时,这种行为是不希望的,并且你想要匹配的字符越少越好。(?!x)
,(?=x)
:阴性和阳性预测先行。{x}
:精确 x 匹配前面的正则表达式的出现。{x,y}
:至少匹配 x 并且最多 y 匹配前面的正则表达式。{x,}
:匹配 x 出现的一个或多个前面的正则表达式。{x}?
,{x,y}?
,{x,}?
:上述表达式的非贪婪版本。
腾讯 QQ 号:纯数字,至少 5 位
{
"type": "string",
"pattern": "^[1-9][0-9]{4,}$"
}
3.3. 数值类型
-
integer
integer 类型用于整数。在 Python 中,类似于该 int 类型。
{ "type": "integer" }
-
number
number 类型可用于任何数字类型,整数或浮点数。在 Python 中,类似于 float 类型。
{ "type": "number" }
-
Multiples
multipleOf 关键字可以将数字限制为给定数字的倍数。可以将其设置为任何正数。
{ "type" : "number", "multipleOf" : 10 }
-
Range
数字范围是使用 minimum 与 maximum 关键字(或 exclusiveMinimum 和 exclusiveMaximum 用于表示互斥范围)的组合来指定的 。
如果 x 是要验证的值,则以下条件必须为 true:
X ≥
minimumx >
exclusiveMinimumX ≤
maximumx <
exclusiveMaximum
虽然我们可以同时指定的 minimum 和 exclusiveMinimum 或两者的 maximum 和 exclusiveMaximum,它并没有真正意义的做到这一点。
{ "type": "number", "minimum": 0, "exclusiveMaximum": 100 } // 取值:0-99 排除 100
3.4. 对象
对象是 JSON 中的映射类型。他们将“键”映射到“值”。在 JSON 中,“键”必须始终为字符串。这些对中的每对通常都称为“属性”。
在 Python 中,“对象”类似于 dict 类型。但是,一个重要的区别是,尽管 Python 词典可以使用任何不可变对象作为键,但在 JSON 中,所有键都必须是字符串。
尽量不要被“对象”一词的两种用法所混淆:Python 使用该词 object 来表示所有事物的通用基类,而在 JSON 中,它仅用于表示从字符串键到值的映射。
{ "type": "object" }
// 类型为字典,key 为字符串
-
属性
properties 关键字定义对象上的属性(键值对)的值是一个对象,其中每个键是属性的名称,每个值是用于验证该属性的 JSON 模式。
例如,假设我们要为由数字,街道名称和街道类型组成的地址定义一个简单的模式:
{ "type": "object", "properties": { "街道": { "type": "string" }, "楼号": { "type": "number" } } } // 成功:{ "街道": "新安街道", "楼号": 123 }、{ "街道": "新安街道", "楼号": 123, "区": "宝安区"} // 失败:{ "街道": "新安街道", "楼号": "123" }
additionalProperties 关键字用于控制的额外的东西,默认情况下,允许任何其他属性。如果 additionalProperties 为 boolean 并将其设置为 false,则不允许使用其他属性。
{ "type": "object", "properties": { "街道": { "type": "string" }, "楼号": { "type": "number" } }, "additionalProperties": false } // 成功:{ "街道": "新安街道", "楼号": 123 } // 失败:{ "街道": "新安街道", "楼号": "123" }、{ "街道": "新安街道", "楼号": 123, "区": "宝安区"}
-
必须的属性
required 关键字可以指定必须存在的属性。
{ "type": "object", "properties": { "姓名": { "type": "string" }, "街道": { "type": "string" }, "楼号": { "type": "number" } }, "required": ["姓名", "街道"] } // 成功:{ "姓名": "张三", "街道": "新安街道" }、{ "姓名": "张三", "街道": "新安街道", "楼号": 123, "区": "宝安区"} // 失败:{ "街道": "新安街道", "楼号": "123" }
-
属性数量
minProperties 和 maxProperties 关键字限制对象上的属性数量 。每个参数都必须是非负整数。
{ "type": "object", "minProperties": 2, "maxProperties": 3 } // 成功:{ "a": 0, "b": 1 }、{ "a": 0, "b": 1, "c": 2 } // 失败:{}、{ "a": 0 }、{ "a": 0, "b": 1, "c": 2, "d": 3 }
-
依赖
dependencies 关键字允许基于某些特殊属性是变化的模式。
JSON 模式中有两种形式的依赖关系:
- 属性依赖项声明如果存在给定的属性,则必须存在某些其他属性。
- 模式依赖项声明存在给定属性时模式会更改。
比如,如果街道存在时,楼号也必须存在。
{ "type": "object", "properties": { "姓名": { "type": "string" }, "街道": { "type": "string" }, "楼号": { "type": "number" } }, "required": ["姓名"], "dependencies": { "街道": ["楼号"] } } // 成功:{ "姓名": "张三"}、{ "姓名": "张三", "街道": "新安街道", "楼号": 123}、{ "姓名": "张三", "楼号": 123} // 失败:{ "姓名": "张三", "街道": "新安街道" }
当然也可以明确定义双向依赖性
{ "type": "object", "properties": { "姓名": { "type": "string" }, "街道": { "type": "string" }, "楼号": { "type": "number" } }, "required": ["姓名"], "dependencies": { "街道": ["楼号"], "楼号": ["街道"] } } // 成功:{ "姓名": "张三"}、{ "姓名": "张三", "街道": "新安街道", "楼号": 123} // 失败:{ "姓名": "张三", "街道": "新安街道" }、{ "姓名": "张三", "楼号": 123}
-
模式依赖
模式依赖项的工作方式类似于属性依赖项,但是它们不仅可以指定其他必需的属性,还可以扩展模式使其具有其他约束。
{ "type": "object", "properties": { "姓名": { "type": "string" }, "街道": { "type": "string" } }, "required": ["姓名"], "dependencies": { "街道": { "properties": { "楼号": { "type": "number" } }, "required": ["楼号"] } } } // 成功:{ "姓名": "张三"}、{ "姓名": "张三", "街道": "新安街道", "楼号": 123} // 失败:{ "姓名": "张三", "街道": "新安街道" }
3.5. 数组
数组用于有序元素。在 JSON 中,数组中的每个元素都可以具有不同的类型。
JSON 中通常使用两种方式使用数组:
-
列表验证:任意长度的序列,其中每个项目都匹配相同的模式。
-
元组验证:固定长度的序列,其中每个项目可能具有不同的模式。在这种用法中,每个项目的索引(或位置)对于如何解释该值都是有意义的。
-
列表验证
定义数组中的每个项目都是一个数字:
{ "type": "array", "items": { "type": "number" } } // 成功:[1, 2, 3, 4, 5]、[] // 失败:[1, 2, "3", 4, 5]
items 模式必须对数组中的每个项目都有效,但是 contains 模式仅需要针对数组中的一个或多个项目进行验证。
{ "type": "array", "contains": { "type": "number" } } // 成功:[1, 2, 3, 4, 5]、["a", 4, "b"] // 失败:["a", "b"]
-
元组验证
当数组是项目的集合时,其中每个项目具有不同的模式并且每个项目的序号索引都有意义,则元组验证很有用。
比如一个描述地理位置的元组:
[街道, 经度, 维度, 朝向]
- 街道:必须是字符串
- 经度:必须是数字
- 纬度:必须是数字
- 朝向:只能是东南西北
{ "type": "array", "items" [ { "type": "string" }, { "type": "number" }, { "type": "number" }, { "type": "string", "enum": ["东", "南", "西", "北"] } ] }
-
长度
可以使用
minItems
maxItems
关键字指定数组的长度。每个关键字的值必须为非负数。{ "type": "array", "minItems": 2, "maxItems": 3 } // 成功:[1, 2]、[1, 2, 3] // 失败:[]、[1]、[1, 2, 3, 4]
-
唯一
可以确保数组中的每个项目都是唯一的。只需将 uniqueItems 关键字设置为即可 true。
{ "type": "array", "uniqueItems": true } // 成功:[1, 2, 3, 4]、[] // 失败:[1, 2, 3, 1]
3.6. 布尔值
布尔类型仅匹配两个特殊值:true 和 false。
{ "type": "boolean" }
// 成功:true、false
// 失败:"true"、0
3.7. 空
空类型通常用于表示缺失值。当模式指定 type 时 null,它只有一个可接受的值:null。
{ "type": "null" }
// 成功:null
// 失败:false、0、""