# 组合结构

¥Combining schemas

# 将结构与 $ref 结合

¥Combining schemas with $ref

你可以跨多个结构文件构建校验逻辑,并使用 $ref 关键字让结构相互引用。

¥You can structure your validation logic across multiple schema files and have schemas reference each other using $ref keyword.

示例:

¥Example:

const schema = {
  $id: "http://example.com/schemas/schema.json",
  type: "object",
  properties: {
    foo: {$ref: "defs.json#/definitions/int"},
    bar: {$ref: "defs.json#/definitions/str"},
  },
}

const defsSchema = {
  $id: "http://example.com/schemas/defs.json",
  definitions: {
    int: {type: "integer"},
    str: {type: "string"},
  },
}

现在要编译你的结构,你可以将所有结构传递给 Ajv 实例:

¥Now to compile your schema you can either pass all schemas to Ajv instance:

const ajv = new Ajv({schemas: [schema, defsSchema]})
const validate = ajv.getSchema("http://example.com/schemas/schema.json")

或使用 addSchema 方法:

¥or use addSchema method:

const ajv = new Ajv()
const validate = ajv.addSchema(defsSchema).compile(schema)

参见 选项addSchema 方法。

¥See Options and addSchema method.

参考解析

  • 使用 schema $id 作为基本 URI,将 $ref 解析为 uri 引用(请参阅示例)。

    ¥$ref is resolved as the uri-reference using schema $id as the base URI (see the example).

  • 引用可以递归(并且相互递归)来实现不同数据结构(例如链表、树、图等)的结构。

    ¥References can be recursive (and mutually recursive) to implement the schemas for different data structures (such as linked lists, trees, graphs, etc.).

  • 你不必将架构文件托管在用作架构 $id 的 URI 处。这些 URI 仅用于标识结构,根据 JSON 结构规范,校验器不应期望能够从这些 URI 下载结构。

    ¥You don't have to host your schema files at the URIs that you use as schema $id. These URIs are only used to identify the schemas, and according to JSON Schema specification validators should not expect to be able to download the schemas from these URIs.

  • 不使用文件系统中结构文件的实际位置。

    ¥The actual location of the schema file in the file system is not used.

  • 你可以将结构的标识符作为 addSchema 方法的第二个参数或作为 schemas 选项中的属性名称传递。该标识符可以用来代替(或补充)模式 $id。

    ¥You can pass the identifier of the schema as the second parameter of addSchema method or as a property name in schemas option. This identifier can be used instead of (or in addition to) schema $id.

  • 你不能将相同的 $id (或模式标识符)用于多个模式 - 将抛出异常。

    ¥You cannot have the same $id (or the schema identifier) used for more than one schema - the exception will be thrown.

  • 你可以使用 compileAsync 方法实现引用结构的动态解析。通过这种方式,你可以将结构存储在任何系统(文件、Web、数据库等)中并引用它们,而无需显式添加到 Ajv 实例。参见 异步结构编译

    ¥You can implement dynamic resolution of the referenced schemas using compileAsync method. In this way you can store schemas in any system (files, web, database, etc.) and reference them without explicitly adding to Ajv instance. See Asynchronous schema compilation.

# 扩展递归结构

¥Extending recursive schemas

虽然静态定义的 $ref 关键字允许将模式拆分为多个文件,但很难扩展递归模式 - 原始模式中的递归引用指向原始模式,而不是扩展模式。因此,在 JSON Schema Draft-07 中,扩展递归结构的唯一可用解决方案是重新定义原始结构中具有递归的所有部分。

¥While statically defined $ref keyword allows to split schemas to multiple files, it is difficult to extend recursive schemas - the recursive reference(s) in the original schema points to the original schema, and not to the extended one. So in JSON Schema draft-07 the only available solution to extend the recursive schema was to redefine all sections of the original schema that have recursion.

在扩展元结构时,它特别重复,因为它有许多递归引用,但即使在具有单个递归引用的结构中,扩展它也非常冗长。

¥It was particularly repetitive when extending meta-schema, as it has many recursive references, but even in a schema with a single recursive reference extending it was very verbose.

JSON Schema Draft-2019-09 和即将发布的草案使用关键字 $recursiveRef/$recursiveAnchor(draft-2019-09)或 $dynamicRef/$dynamicAnchor(下一个 JSON Schema 草案)定义了动态递归的机制,这与函数式编程中的 "开放递归" 有点相似。

¥JSON Schema draft-2019-09 and the upcoming draft defined the mechanism for dynamic recursion using keywords $recursiveRef/$recursiveAnchor (draft-2019-09) or $dynamicRef/$dynamicAnchor (the next JSON Schema draft) that is somewhat similar to "open recursion" in functional programming.

考虑这个具有静态递归的递归结构:

¥Consider this recursive schema with static recursion:

const treeSchema = {
  $id: "https://example.com/tree",
  type: "object",
  required: ["data"],
  properties: {
    data: true,
    children: {
      type: "array",
      items: {$ref: "#"},
    },
  },
}

扩展此架构以禁止其他属性的唯一方法是在架构中添加 additionalProperties 关键字 - 如果你不控制原始模式的来源,这种方法可能是不可能的。Ajv 还在 ajv-merge-patch (opens new window) 包中提供了附加关键字,通过将结构视为纯 JSON 数据来扩展结构。虽然这种方法可能适合你,但它是非标准的,因此不可移植。

¥The only way to extend this schema to prohibit additional properties is by adding additionalProperties keyword right in the schema - this approach can be impossible if you do not control the source of the original schema. Ajv also provided the additional keywords in ajv-merge-patch (opens new window) package to extend schemas by treating them as plain JSON data. While this approach may work for you, it is non-standard and therefore not portable.

动态递归引用的新关键字允许扩展此结构而不修改它:

¥The new keywords for dynamic recursive references allow extending this schema without modifying it:

const treeSchema = {
  $id: "https://example.com/tree",
  $recursiveAnchor: true,
  type: "object",
  required: ["data"],
  properties: {
    data: true,
    children: {
      type: "array",
      items: {$recursiveRef: "#"},
    },
  },
}

const strictTreeSchema = {
  $id: "https://example.com/strict-tree",
  $recursiveAnchor: true,
  $ref: "tree",
  unevaluatedProperties: false,
}

import Ajv2019 from "ajv/dist/2019"
// const Ajv2019 = require("ajv/dist/2019")
const ajv = new Ajv2019({
  schemas: [treeSchema, strictTreeSchema],
})
const validate = ajv.getSchema("https://example.com/strict-tree")

有关使用 $dynamicAnchor/$dynamicRef 的示例,请参阅 dynamic-refs (opens new window) 测试。

¥See dynamic-refs (opens new window) test for the example using $dynamicAnchor/$dynamicRef.

目前,Ajv 实现了动态递归引用的规范,但有以下限制:

¥At the moment Ajv implements the spec for dynamic recursive references with these limitations:

  • $recursiveAnchor/$dynamicAnchor 只能在结构根中使用。

    ¥$recursiveAnchor/$dynamicAnchor can only be used in the schema root.

  • $recursiveRef/$dynamicRef 只能是哈希片段,没有 URI。

    ¥$recursiveRef/$dynamicRef can only be hash fragments, without URI.

Ajv 也不支持 异步结构 中的动态引用(Ajv 扩展) - 假设引用的模式是同步的,并且没有对其进行校验时检查。

¥Ajv also does not support dynamic references in asynchronous schemas (Ajv extension) - it is assumed that the referenced schema is synchronous, and there is no validation-time check for it.

# $data 参考

¥$data reference

通过 $data 选项,你可以使用已校验数据中的值作为结构关键字的值。有关其工作原理的更多信息,请参阅 proposal (opens new window)

¥With $data option you can use values from the validated data as the values for the schema keywords. See proposal (opens new window) for more information about how it works.

关键字中支持 $data 引用:const、枚举、格式、最大/最小值、exclusiveMaximum/exclusiveMinimum、maxLength/minLength、maxItems/minItems、maxProperties/minProperties、formatMaximum/formatMinimum、formatExclusiveMaximum/formatExclusiveMinimum、multipleOf、pattern、required、uniqueItems。

¥$data reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems.

"$data" 的值应该是数据的 JSON-pointer (opens new window)(根始终是顶层数据对象,即使 $data 引用位于引用的子结构内)或 相对 JSON 指针 (opens new window)(它相对于数据中的当前点;如果 $data 引用位于引用的子模式内部,则它不能指向该子模式的根级别之外的数据)。

¥The value of "$data" should be a JSON-pointer (opens new window) to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a relative JSON-pointer (opens new window) (it is relative to the current point in data; if the $data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema).

例子。

¥Examples.

此结构要求属性 smaller 中的值小于或等于较大属性中的值:

¥This schema requires that the value in property smaller is less or equal than the value in the property larger:

const ajv = new Ajv({$data: true})

const schema = {
  properties: {
    smaller: {
      type: "number",
      maximum: {$data: "1/larger"},
    },
    larger: {type: "number"},
  },
}

const validData = {
  smaller: 5,
  larger: 7,
}

ajv.validate(schema, validData) // true

此结构要求属性与其字段名称具有相同的格式:

¥This schema requires that the properties have the same format as their field names:

const schema = {
  additionalProperties: {
    type: "string",
    format: {$data: "0#"},
  },
}

const validData = {
  "date-time": "1963-06-19T08:30:06.283185Z",
  email: "joe.bloggs@example.com",
}

$data 引用已安全解析 - 即使某些属性未定义,它也不会抛出。如果 $data 解析为 undefined,则校验成功(排除 const 关键字)。如果 $data 解析为不正确的类型(例如,最大关键字不是 "number"),则校验失败。

¥$data reference is resolved safely - it won't throw even if some property is undefined. If $data resolves to undefined the validation succeeds (with the exclusion of const keyword). If $data resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails.

# $merge 和 $patch 关键字

¥$merge and $patch keywords

使用 ajv-merge-patch (opens new window) 包,你可以使用关键字 $merge$patch,它们允许使用格式 JSON 合并补丁 (RFC 7396) (opens new window)JSON 补丁(RFC 6902) (opens new window) 的补丁来扩展 JSON 结构。

¥With the package ajv-merge-patch (opens new window) you can use the keywords $merge and $patch that allow extending JSON Schemas with patches using formats JSON Merge Patch (RFC 7396) (opens new window) and JSON Patch (RFC 6902) (opens new window).

要将关键字 $merge$patch 添加到 Ajv 实例,请使用以下代码:

¥To add keywords $merge and $patch to Ajv instance use this code:

require("ajv-merge-patch")(ajv)

例子。

¥Examples.

使用 $merge

¥Using $merge:

{
  $merge: {
    source: {
      type: "object",
      properties: {p: {type: "string"}},
      additionalProperties: false
    },
    with: {
      properties: {q: {type: "number"}}
    }
  }
}

使用 $patch

¥Using $patch:

{
  $patch: {
    source: {
      type: "object",
      properties: {p: {type: "string"}},
      additionalProperties: false
    },
    with: [{op: "add", path: "/properties/q", value: {type: "number"}}]
  }
}

上面的结构相当于这个结构:

¥The schemas above are equivalent to this schema:

{
  type: "object",
  properties: {
    p: {type: "string"},
    q: {type: "number"}
  },
  additionalProperties: false
}

关键字 $merge$patch 中的属性 sourcewith 可以使用绝对或相对 $ref 来指向先前添加到 Ajv 实例的其他结构或当前结构的片段。

¥The properties source and with in the keywords $merge and $patch can use absolute or relative $ref to point to other schemas previously added to the Ajv instance or to the fragments of the current schema.

有关详细信息,请参阅包 ajv-merge-patch (opens new window)

¥See the package ajv-merge-patch (opens new window) for more information.