JSON Schema 进阶

Posted 戴泽supp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSON Schema 进阶相关的知识,希望对你有一定的参考价值。

JSON Schema 进阶

一、Media:字符串编码非 JSON 数据

1、内容媒体类型

contentMediaType 关键字指定的MIME类型的字符串的内容,如在RFC 2046。有一个由 IANA 正式注册MIME 类型列表,但支持的类型集将取决于应用程序和操作系统。Mozilla Developer Network 还维护了一个较短的对网络很重要的 MIME 类型列表

2、内容编码

contentEncoding关键字指定编码用于存储内容,如在规定的RFC 2054,部分6.1RFC 4648

可接受的值为7bit8bitbinaryquoted-printablebase16base32,和base64。如果未指定,则编码与包含的 JSON 文档相同。

在不深入了解每种编码的底层细节的情况下,实际上只有两个选项对现代使用有用:

  • 如果内容使用与封闭 JSON 文档相同的编码(出于实际目的,几乎总是 UTF-8),请保持 contentEncoding未指定,并将内容按原样包含在字符串中。这包括基于文本的内容类型,例如text/htmlapplication/xml
  • 如果内容是二进制数据,则设置contentEncodingbase64并使用Base64对内容进行编码。这将包括许多图像类型,例如image/png或音频类型,例如audio/mpeg.

3、 内容架构

以下模式指示字符串包含一个 HTML 文档,使用与周围文档相同的编码进行编码:


  "type": "string",
  "contentMediaType": "text/html"


// OK
"<!DOCTYPE html><html xmlns=\\"http://www.w3.org/1999/xhtml\\"><head></head></html>"

以下模式指示字符串包含使用 Base64 编码的PNG图像:


  "type": "string",
  "contentEncoding": "base64",
  "contentMediaType": "image/png"


// OK
"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA..."

二、Schema 组合

JSON Schema 包含一些用于将模式组合在一起的关键字。通常可以使用这些关键字来表达无法用标准 JSON Schema 关键字表达的复杂约束。

用于组合模式的关键字是:

  • allOf: (AND) 必须对_所有_子模式有效
  • anyOf : (OR) 必须对_任何子_模式有效
  • oneOf : (XOR) 必须对_恰好一个_子模式有效
  • not: (NOT)_不能_对给定的模式有效

1、allOf

allOf,给定的数据必须针对给定的所有子模式有效。


  "allOf": [
     "type": "string" ,
     "maxLength": 5 
  ]


"short" // OK
"too long"  // not OK

2、 anyOf

anyOf,数据必须满足任意一个或多个给定子模式。


  "anyOf": [
     "type": "string", "maxLength": 5 ,
     "type": "number", "minimum": 0 
  ]


"short" // OK
"too long"  // not OK
12 // OK
-5  // not OK

3、oneOf

oneOf,数据必须满足且只满足一个给定的子模式。


  "oneOf": [
     "type": "number", "multipleOf": 5 ,
     "type": "number", "multipleOf": 3 
  ]


10 // OK
9 // OK

2  // not OK,不是 5 或 3 的倍数。
15  // not OK,同时符合两个子模式被拒绝。

4、not

not,数据不能满足给定的子模式。

 "not":  "type": "string" 

42 // OK
 "key": "value"  // OK
"I am a string"  // not OK

5、模式组合的属性

1)、子模式独立

注意:allOfanyOfoneOf 数组中列出的模式彼此一无所知。例如,假设您在一个$defs部分中有一个地址的架构,并且想要“扩展”它以包含地址类型:


  "$defs": 
    "address": 
      "type": "object",
      "properties": 
        "street_address":  "type": "string" ,
        "city":  "type": "string" ,
        "state":  "type": "string" 
      ,
      "required": ["street_address", "city", "state"]
    
  ,
  "allOf": [
     "$ref": "#/$defs/address" ,
    
      "properties": 
        "type":  "enum": [ "residential", "business" ] 
      
    
  ]



// OK
   "street_address": "1600 Pennsylvania Avenue NW",
   "city": "Washington",
   "state": "DC",
   "type": "business"

限制模式以便不允许附加属性怎么办?可以尝试添加"additionalProperties": false


  "$defs": 
    "address": 
      "type": "object",
      "properties": 
        "street_address":  "type": "string" ,
        "city":  "type": "string" ,
        "state":  "type": "string" 
      ,
      "required": ["street_address", "city", "state"]
    
  ,

  "allOf": [
     "$ref": "#/$defs/address" ,
    
      "properties": 
        "type":  "enum": [ "residential", "business" ] 
      
    
  ],

  "additionalProperties": false


 // not OK

   "street_address": "1600 Pennsylvania Avenue NW",
   "city": "Washington",
   "state": "DC",
   "type": "business"

现在模式将拒绝_一切_。这是因为 additionalPropertiesallOf 数组内的子模式中声明的属性一无所知。

2)、不合逻辑的模式

以下示例创建了一个不会针对任何内容进行验证的架构(因为某些内容可能不会同时是字符串和数字):


  "allOf": [
     "type": "string" ,
     "type": "number" 
  ]


"No way"  // not OK
-1  // not OK

3)、分解模式

可以“分解”子模式的公共部分。以下两个模式是等效的。


  "oneOf": [
     "type": "number", "multipleOf": 5 ,
     "type": "number", "multipleOf": 3 
  ]



   "type": "number",
   "oneOf": [
      "multipleOf": 5 ,
      "multipleOf": 3 
   ]
 

三、有条件地应用子模式

1、必要依赖

dependentRequired 关键字有条件地要求,如果一个对象存在某个特定的属性,则另一个属性也必须存在。dependentRequired 关键字的值是一个对象。对象中的每个条目都从属性的名称 p 映射到一个字符串数组,其中列出了 p 存在时所需的属性。

在下面的例子中,无论何时,只要存在 credit_card,另一个属性 billing_address 属性必须存在:


  "type": "object",

  "properties": 
    "name":  "type": "string" ,
    "credit_card":  "type": "number" ,
    "billing_address":  "type": "string" 
  ,

  "required": ["name"],

  "dependentRequired": 
    "credit_card": ["billing_address"]
  


// OK

  "name": "John Doe",
  "credit_card": 5555555555555555,
  "billing_address": "555 Debtor's Lane"


 // not OK,这个实例有一个credit_card,但缺少一个billing_address。

  "name": "John Doe",
  "credit_card": 5555555555555555


// OK。这没关系,因为我们既没有credit_carda也没有billing_address。

  "name": "John Doe"


// OK。请注意,依赖项不是双向的。有一个没有信用卡号的帐单地址是可以的。

  "name": "John Doe",
  "billing_address": "555 Debtor's Lane"

要解决上面的最后一个问题(依赖项不是双向的),您当然可以明确定义双向依赖项:


  "type": "object",

  "properties": 
    "name":  "type": "string" ,
    "credit_card":  "type": "number" ,
    "billing_address":  "type": "string" 
  ,

  "required": ["name"],

  "dependentRequired": 
    "credit_card": ["billing_address"],
    "billing_address": ["credit_card"]
  



 // not OK,这个实例有一个credit_card,但缺少一个billing_address。

  "name": "John Doe",
  "credit_card": 5555555555555555


 // not OK,这有一个billing_address,但缺少一个credit_card。

  "name": "John Doe",
  "billing_address": "555 Debtor's Lane"

2、模式依赖

dependenciesSchemas 关键字要求当给定的属性存在时,有条件地应用子模式。此架构的应用方式与 allOf 应用架构的方式相同。没有合并或扩展任何内容。两种模式独立应用。

例如,这里有另一种写法:


  "type": "object",

  "properties": 
    "name":  "type": "string" ,
    "credit_card":  "type": "number" 
  ,

  "required": ["name"],

  "dependentSchemas": 
    "credit_card": 
      "properties": 
        "billing_address":  "type": "string" 
      ,
      "required": ["billing_address"]
    
  


// OK

  "name": "John Doe",
  "credit_card": 5555555555555555,
  "billing_address": "555 Debtor's Lane"


 // not OK,这个实例有一个credit_card,但缺少一个 billing_address:

  "name": "John Doe",
  "credit_card": 5555555555555555


// OK。这有一个billing_address,但缺少一个 credit_card。这通过了,因为这里billing_address 看起来像一个附加属性:

  "name": "John Doe",
  "billing_address": "555 Debtor's Lane"

3、条件语句

新的Draft7中 ifthenelse关键字允许基于另一种模式的结果来应用子模式,这很像传统编程语言中的if/ then/else构造。

如果if有效,then也必须有效(并被else忽略)。如果 if无效,else也必须有效(并被then忽略)。

如果thenelse未定义,则if表现为它们的值为true

如果then和/或else出现在没有if,then和 的模式中,else则被忽略。

显示 when if, then, and elseare valid的组合 以及整个模式的结果有效性:

ifthenelsewhole schema
TTn/aT
TFn/aF
Fn/aTT
Fn/aFF
n/an/an/aT

例如,假设您想编写一个模式来处理美国和加拿大的地址。这些国家/地区有不同的邮政编码格式,我们希望根据国家/地区选择要验证的格式。如果地址在美国,则该postal_code 字段是“邮政编码”:五个数字后跟可选的四位后缀。如果地址在加拿大,则该 postal_code 字段是一个六位字母数字字符串,其中字母和数字交替出现。


  "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]"  
  


// OK

  "street_address": "1600 Pennsylvania Avenue NW",
  "country": "United States of America",
  "postal_code": "20500"


// OK

  "street_address": "1600 Pennsylvania Avenue NW",
  "postal_code": "20500"


// OK

  "street_address": "24 Sussex Drive",
  "country": "Canada",
  "postal_code": "K1M 1M4"


 // not OK

  "street_address": "24 Sussex Drive",
  "country": "Canada",
  "postal_code": "10000"

// not OK

  "street_address": "1600 Pennsylvania Avenue NW",
  "postal_code": "K1M 1M4"

注意:在此示例中,“国家/地区”不是必需的属性。因为“if”模式也不需要“country”属性,它会pass然后应用“then”模式。因此,如果未定义“country”属性,则默认行为是将“postal_code”验证为美国邮政编码。“default”关键字没有效果,但将其包含在模式中,对读者比较友好,可以更容易地识别默认行为。

不幸的是,上面的这种方法不能扩展到两个以上的国家。但是,您可以将ifthen包裹在allOf以创建可扩展的内容。在此示例中,我们将使用美国和加拿大邮政编码,但还会添加荷兰邮政编码,即 4 位数字后跟两个字母。读者可以尝试练习将其扩展到世界上其余的邮政编码。


  "type": "object",
  "properties": 
    "street_address": 
      "type": "string"
    ,
    "country": 
      "default": "United States of America",
      "enum": ["United States of America", "Canada", "Netherlands"]
    
  ,
  "allOf": [
    
      "if": 
        "properties":  "country":  "const": "United States of America"  
      ,
      "then": 
        "properties":  "postal_code":  "pattern": "[0-9]5(-[0-9]4)?"  
      
    ,
    
      "if": 
        "properties":  "country":  "const": "Canada"  ,
        "required": ["country"]
      ,
      "then": 
        "properties":  "postal_code":  "pattern": "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]"  
      
    ,
    
      "if": 
        "properties":  "country":  "const": "Netherlands"  ,
        "required": ["country"]
      ,
      "then": 
        "properties":  "postal_code":  "pattern": "[0-9]4 [A-Z]2"  
      
    
  ]


// OK

  "street_address": "1600 Pennsylvania Avenue NW",
  "country": "United States of America",
  "postal_code": "20500"


// OK

  "street_address": "1600 Pennsylvania Avenue NW",
  "postal_code": "20500"


// OK

  "street_address": "24 Sussex Drive",
  "country": "Canada",
  "postal_code": "K1M 1M4"


// OK

  "street_address": "Adriaan Goekooplaan",
  "country": "Netherlands",
  "postal_code": "2517 JX"


 // not OK

  "street_address": "24 Sussex Drive",
  "country": "Canada",
  "postal_code": "10000"


 // not OK

  "street_address": "1600 Pennsylvania Avenue NW",
  "postal_code": "K1M 1M4"

注意:

  • if 模式中的“required”关键字是必需的,否则如果未定义“country”,则它们都将适用。如果未定义“country”,则将“required”从“United States of America”“IF”模式中删除,使其有效地成为默认值。

  • 即使“country”是必填字段,仍然建议在每个“if”模式中使用“required”关键字。验证结果将相同,因为“required”将失败,但不包括它会增加错误结果的噪音,因为它将针对所有三个“then”模式验证“postal_code”,导致不相关的错误。

4、蕴含

在 Draft 7 之前,您可以使用模式组合关键字和称为“蕴含”的布尔代数概念来表达“if-then”条件 。A -> B(A 隐含 B)意味着如果 A 为真,那么 B 也必须为真。它表示为 JSON Schema可以这样写 !A || B


  "type": "object",
  "properties": 
    "restaurantType":  "enum": ["fast-food", "sit-down"] ,
    "total":  "type": "number" ,
    "tip":  "type": "number" 
  ,
  "anyOf": [
    
      "not": 
        "properties":  "restaurantType":  "const": "sit-down"  ,
        "required": ["restaurantType"]
      
    ,
     "required": ["tip"] 
  ]


 // OK

  "restaurantType": "sit-down",
  "total": 16.99,
  "tip": 3.4


 // not OK

  "restaurantType": "sit-down",
  "total": 16.99


 // OK

  "restaurantType": "fast-food",
  "total": 6.99

// OK
 "total": 5.25 

四、声明方言

JSON Schema 的一个版本称为方言。方言表示可用于评估模式的一组关键字和语义。每个 JSON Schema 版本都是 JSON Schema 的新方言。JSON Schema 为您提供了一种声明模式符合哪种方言的方法,并提供了描述您自己的自定义方言的方法。

1、$schema

$schema关键字用于声明模式是针对哪种 JSON 方言编写的。$schema 关键字的值也是模式的标识符,可用于根据方言 $schema 标识验证模式是否有效。描述另一个模式的模式称为“元模式”。

$schema 适用于整个文档并且必须在根级别。它不适用于外部引用的 ( $ref, $recursiveRef) 文档。这些模式需要声明自己的 $schema.

如果 $schema 未使用,则实现可能允许您在外部指定一个值,或者它可能会假设应该使用哪个规范版本来评估模式。建议所有 JSON 模式都有一个 $schema 关键字来与读者和工具进行交流,以了解预期的规范版本。因此,大多数情况下,您会希望在架构的根目录下使用它:

"$schema": "https://json-schema.org/draft/2019-09/schema"
//Draft 4 的标识符是http://json-schema.org/draft-04/schema#。
//没有 JSON Schema 的 Draft 5 版本。Draft 5 指的是Draft 4 版本的无变化修订版
//Draft 6 的标识符是http://json-schema.org/draft-06/schema#。
//Draft 7 的标识符是http://json-schema.org/draft-07/schema#。

2、词汇表

1)、指南

JSON Schema 的优势之一是它可以用 JSON 编写并在各种环境中使用。例如,它可用于前端和后端 HTML 表单验证。使用自定义词汇表的问题在于,您想要使用模式的每个环境都需要了解如何评估词汇表的关键字。元模式可用于确保模式编写正确,但每个实现都需要自定义代码来了解如何评估词汇表的关键字。

元数据关键字是最具互操作性的,因为它们不影响验证。例如,您可以添加 units 关键字。对于合规的验证器,将始终按预期生效。


  "type": "number",
  "units": "kg"


42 // OK
"42"  // not OK

自定义关键字的下一个最佳候选者是不应用其他模式且不修改现有关键字行为的关键字。isEven 关键字是一个例子,在某些语境下验证比没有验证要好,例如在浏览器中验证 HTML 表单时,此模式的性能将达到预期。完全验证仍然是需要的,并且应该使用可以理解自定义关键字的验证器。


  "type": "integer",
  "isEven": true


2 // OK
3 // OK,这通过因为验证器不理解 `isEven`
"3"  // not OK,模式没有因为不理解 `isEven`而完全受损

互操作性最差的自定义关键字类型是应用其他模式或修改现有关键字行为的自定义关键字。一个例子就是requiredProperties,这个关键字声明属性并使它们成为必需属性。

以下示例显示了在使用不理解自定义关键字的验证器进行校验时,模式如何变得几乎无用。这并不一定意味着这requiredProperties对关键字来说是个坏主意,只是说如果模式在不理解自定义关键字的上下文中使用时不是一个好的选择。


  "type": "object",
  "requiredProperties": 
    "foo":  "type": "string"
  


 "foo": "bar"  // OK
 // OK,因为`requiredProperties`不被理解
 "foo": 42  //ok,因为`requiredProperties`不被理解

五、构建复杂模式

1、模式识别

与任何其他代码一样,将模式分解为在必要时相互引用的逻辑单元,则模式更易于维护。为了引用模式,我们需要一种识别模式的方法。模式文档由非相对 URI 所标识。

模式文档不需要有标识符,但如果您想从另一个模式引用一个模式,则需要一个标识符。在本文档中,我们将没有标识符的模式称为“匿名模式”。

在以下部分中,我们将看到如何确定模式的“标识符”。

注意:尽管模式由 URI 标识,但这些标识符不一定是网络可寻址的。它们只是标识符。通常,实现不会发出 HTTP 请求 ( https://) 或从文件系统 ( file://) 读取以获取模式。相反,它们提供了一种将模式加载到内部模式数据库中的方法。当模式被其 URI 标识符引用时,将从内部架构数据库中检索该模式。

1)、JSON 指针

除了标识模式文档,您还可以标识子模式。最常见的方法是在指向子模式的 URI 片段中使用JSON 指针

JSON 指针描述了一个以斜线分隔的路径来遍历文档中对象中的键。因此, /properties/street_address意味着:

  1. 找到键的值 properties
  2. 在该对象中,找到键的值 street_address

URI https://example.com/schemas/address#/properties/street_address 标识以下模式中子模式 "type": "string"


    "$id": "https://example.com/schemas/address", 
    "type": "object", 
    "properties": 
        "street_address": 
            "type": "string"
        , 
        "city": 
            "type": "string"
        , 
        "state": 
            "type": "string"
        
    , 
    "required": [
        "street_address", 
        "city", 
        "state"
    ]

2)、$锚点

标识子模式的一种不太常见的方法是使用 $anchor 关键字并在 URI 片段中使用该名称在模式中创建命名锚点。锚点必须以字母开头,后跟任意数量的字母、数字、-_:、 或.

注意:在Draft 4 中,您以与Draft 6-7 中相同的方式声明锚点,$id只是只是id(没有美元符号)。在Draft 6-7 中,使用$id仅包含 URI 片段的定义了命名锚点。URI 片段的值是锚点的名称。

URI https://example.com/schemas/address#street_address 标识以下模式的子模式

“$anchor”: “#street_address”, “type”: “string”


    "$id": "https://example.com/schemas/address", 
    "type": "object", 
    "properties": 
        "street_address": 
            "$anchor": "#street_address", 
            "type": "string"
        , 
        "city": 
            "type": "string"
        , 
        "state": 
            "type": "string"
        
    , 
    "required": [
        "street_address", 
        "city", 
        "state"
    ]

2、基本 URI

使用非相对 URI 可能很麻烦,因此 JSON 模式中使用的任何 URI 都可以是 URI 引用,根据模式的基本 URI 进行解析,从而产生非相对 URI。本节介绍如何确定架构的基本 URI。

1)、检索 URI

用于获取模式的 URI 称为“检索 URI”。通常可以将匿名模式传递给实例,在这种情况下,该模式将没有检索 URI。

让我们假设使用 URI 引用 https://example.com/schemas/address 模式并检索以下模式。


    "type": "object", 
    "properties": 
        "street_address": 
            "type": "string"
        , 
        "city": 
            "type": "string"
        , 
        "state": 
            "type": "string"
        
    , 
    "required": [
        "street_address", 
        "city", 
        "state"
    ]

2)、$id

让我们假设 URIhttps://example.com/schema/addresshttps://example.com/schema/billing-address两者都标识以下模式。


    "$id": "/schemas/address", 
    "type": "object", 
    "properties": 
        "street_address": 
            "type": "string"
        , 
        "city": 
            "type": "string"
        , 
        "state": 
            "type": "string"
        
    , 
    "required": [
        "street_address", 
        "city", 
        "state"
    ]

无论使用两个 URI 中的哪一个来检索此模式,基本 URI 都将是https://example.com/schemas/address,这是$id针对检索 URI的URI 引用解析 的结果。

但是,在设置基本 URI 时使用相对引用可能会出现问题。例如,我们不能将此模式用作匿名模式,因为没有检索 URI并且您无法解析相对引用。出于这个原因和其他原因,建议您在使用$id声明基本URI时尽量使用绝对URI.

无论检索 URI是什么 或者它是否用作匿名模式,以下模式的基本 URI 将始终是https://example.com/schemas/address 。


    "$id": "https://example.com/schemas/address", 
    "type": "object", 
    "properties": 
        "street_address": 
            "type": "string"
        , 
        "city": 
            "type": "string"
        , 
        "state": 
            "type": "string"
        
    , 
    "required": [
        "street_address", 
        "city", 
        "state"
    ]

3、$ref

一个模式可以使用$ref关键字引用另一个模式。$ref的值是根据模式的Base URI解析的 URI 引用。当获取$ref的值时,一个实现是使用解析的标识符来检索引用的模式并将该模式应用于实例中。

Draft 4-7 中,$ref表现略有不同。当一个对象包含一个$ref属性时,该对象被认为是一个引用,而不是一个模式。因此,您放入该对象的任何其他属性都不会被视为 JSON 模式关键字,并且会被验证器忽略。$ref只能在需要模式的地方使用。

在这个例子中,假设我们要定义一个客户记录,其中每个客户可能都有一个送货地址和一个账单地址。地址总是相同的——它们有街道地址、城市和州——所以我们不想在我们想要存储地址的任何地方复制模式的那部分。这不仅会使模式更加冗长,而且会使将来更新它变得更加困难。如果我们想象中的公司将来开始从事国际业务,并且我们想为所有地址添加一个国家/地区字段,那么最好在一个地方而不是在使用地址的所有地方进行此操作。


    "$id": "https://example.com/schemas/customer", 
    "type": "object", 
    "properties": 
        "first_name": 
            "type": "string"
        , 
        "last_name": 
            "type": "string"
        , 
        "shipping_address": 
            "$ref": "/schemas/address"
        , 
        "billing_address": 
            "$ref": "/schemas/address"
        
    , 
    "required": [
        "first_name", 
        "last_name", 
        "shipping_address", 
        "billing_address"
    ]

$ref中的URI 引用根据模式的基本 URI ( https://example.com/schemas/customer)进行解析,结果为 https://example.com/schemas/address. 该实现检索该模式并使用它来获取“shipping_address”和“billing_address”属性的值。

笔记 $ref在匿名模式中使用时,相对引用可能无法解析。假设此示例用作匿名模式。


  "type": "object",
  "properties": 
    "first_name":  "type": "string" ,
    "last_name":  "type": "string" ,
    "shipping_address":  "$ref": "https://example.com/schemas/address" ,
    "billing_address":  "$ref": "/schemas/address" 
  ,
  "required": ["first_name", "last_name", "shipping_address", "billing_address"]

在/properties/shipping_address的在没有非相对基础解析时解析是可以的,但中的ref无法解析到一个非相对URI,因此无法用于检索address模式。

4、$defs

有时,我们有一小段仅用于当前模式的子模式,将它们定义为单独的模式是没有意义的。虽然我们可以使用 JSON 指针或命名锚点来识别任何子模式,但$defs关键字为我们提供了一个标准化的位置来保存想在当前模式文档中复用的子模式。

让我们扩展之前的客户模式示例,以使用名称属性的通用架构。为此定义一个新模式没有意义,它只会在这个模式中使用,所以使用$defs非常合适。


    "$id": "https://example.com/schemas/customer", 
    "type": "object", 
    "properties": 
        "first_name": 
            "$ref": "#/$defs/name"
        , 
        "last_name": 
            "$ref": "#/$defs/name"
        , 
        "shipping_address": 
            "$ref": "/schemas/address"
        , 
        "billing_address": 
            "$ref": "/schemas/address"
        
    , 
    "required": [
        "first_name", 
        "last_name", 
        "shipping_address", 
        "billing_address"
    ], 
    "$defs": 
        "name": 
            "type": "string"
        
    

$ref不仅有助于避免重复。它对于编写更易于阅读和维护的模式也很有用。模式的复杂部分可以$defs用描述性名称定义并在需要的地方引用。这允许模式的读者在深入研究更复杂的部分之前,更快速、更轻松地在高层次上理解模式。

笔记 可以引用外部子模式,但通常您希望将 a 限制$ref为引用外部模式或$defs.

5、递归

$ref关键字可以被用来创建一个自我递归模式。例如,您可能有一个person模式包含一个children的数组,每个children也是person 的实例。


  "type": "object",
  "properties": 
    "name":  "type": "string" ,
    "children": 
      "type": "array",
      "items":  "$ref": "#" 
    
  

英国王室的家庭树片段


  "name": "Elizabeth",
  "children": [
    
      "name": "Charles",
      "children": [
        
          "name": "William",
          "children": [
             "name": "George" ,
             "name": "Charlotte" 
          ]
        ,
        
          "name": "Harry"
        
      ]
    
  ]

上面,我们创建了一个引用自身的模式,有效地在验证器中创建了一个“循环”,这既允许又有用。但是请注意,$ref对另一个的引用$ref可能会导致解析器中的无限循环,并且是明确禁止的。


  "$defs": 
    "alice":  "$ref": "#/$defs/bob" ,
    "bob":  "$ref": "#/$defs/alice" 
  

6、捆绑

使用多个模式文档便于开发,但将所有模式捆绑到单个模式文档中通常更方便分发。这可以通过在子模式中使用关键字来完成。当id`在子模式中使用时,它表示嵌入式模式。

嵌入式模式的标识符是根据它出现在其中的模式的基本URI解析的得到的$id的值。包含嵌入模式的模式文档称为复合模式文档,复合架构文档中每个带有$id的模式称为模式资源。

示例显示捆绑到复合模式文档中的客户模式示例和地址模式示例。

  "$id": "https://example.com/schemas/customer",
  "$schema": "https://json-schema.org/draft/2019-09/schema",

  "type": "object",
  "properties": 
    "first_name":  "type": "string" ,
    "last_name":  "type": "string" ,
    "shipping_address":  "$ref": "/schemas/address" ,
    "billing_address":  "$ref": "/schemas/address" 
  ,
  "required": ["first_name", "last_name", "shipping_address", "billing_address"],

  "$defs": 
    "address": 
      "$id": "/schemas/address",
      "$schema": "http://json-schema.org/draft-07/schema#",

      "type": "object",
      "properties": 
        "street_address":  "type": "string" ,
        "city":  "type": "string" ,
        "state":  "$ref": "#/definitions/state" 
      ,
      "required": ["street_address", "city", "state"],

      "definitions": 
        "state":  "enum": ["CA", "NY", "... etc ..."] 
      
    
  

以上是关于JSON Schema 进阶的主要内容,如果未能解决你的问题,请参考以下文章

JSON Schema 提取必填字段

接口测试框架实战| 搞定 Schema 断言

Django---进阶8

Django---进阶8

来自 JSON 或 JSON-Schema 的普通旧 Java 对象

第19天SQL进阶-查询优化- performance_schema系列一:了解performance_schema(SQL 小虚竹)