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 ≥ minimum
    • x > exclusiveMinimum
    • X ≤ maximum
    • x < 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、""
© 2022 刘士. All rights reserved.

结果匹配 ""

    没有匹配的结果 ""