# 独立校验代码

¥Standalone validation code

Ajv 支持在编译/构建时从 JSON 结构生成独立的校验函数。然后可以在运行时使用这些函数进行校验,而无需初始化 Ajv。它很有用,有几个原因:

¥Ajv supports generating standalone validation functions from JSON Schemas at compile/build time. These functions can then be used during runtime to do validation without initialising Ajv. It is useful for several reasons:

  • 减少浏览器包的大小 - Ajv 不包含在打包包中(尽管如果你有大量模式,打包包可能会变得更大 - 当生成的校验代码的总大小大于 Ajv 代码时)。

    ¥to reduce the browser bundle size - Ajv is not included in the bundle (although if you have a large number of schemas the bundle can become bigger - when the total size of generated validation code is bigger than Ajv code).

  • 减少启动时间 - 模式的校验和编译将在构建期间进行。

    ¥to reduce the start-up time - the validation and compilation of schemas will happen during build time.

  • 避免使用函数构造函数进行动态代码评估(用于模式编译) - 当浏览器页面 内容安全政策 禁止时。

    ¥to avoid dynamic code evaluation with Function constructor (used for schema compilation) - when it is prohibited by the browser page Content Security Policy.

Ajv v7 中的此功能取代了可与 Ajv v6 一起使用的已弃用的包 ajv-pack。支持所有结构,包括那些具有递归引用、格式和用户定义关键字的结构,只有少数 limitations

¥This functionality in Ajv v7 supersedes the deprecated package ajv-pack that can be used with Ajv v6. All schemas, including those with recursive references, formats and user-defined keywords are supported, with few limitations.

# 两步过程

¥Two-step process

第一步是生成独立的校验函数代码。这是在项目的编译/构建时完成的,输出是生成的 JS 文件。第二步是使用生成的 JS 校验函数。

¥The first step is to generate the standalone validation function code. This is done at compile/build time of your project and the output is a generated JS file. The second step is to use the generated JS validation function.

有两种方法可以生成代码:使用 Ajv CLI 或 Ajv JS 库。生成代码时还可以传递一些不同的选项。以下只是几个选项的重点:

¥There are two methods to generate the code, using either the Ajv CLI or the Ajv JS library. There are also a few different options that can be passed when generating code. Below is just a highlight of a few options:

  • code.source (JS) 值设置为 true 或使用 compile (CLI) 命令生成独立代码。

    ¥Set the code.source (JS) value to true or use the compile (CLI) command to generate standalone code.

  • 独立代码可以用 ES5 或 ES6 生成,默认为 ES6。如果你需要 ES5 代码,请将 code.es5 (JS) 值设置为 true 或将 --code-es5 (CLI) 标志传递为 true。

    ¥The standalone code can be generated in either ES5 or ES6, it defaults to ES6. Set the code.es5 (JS) value to true or pass the --code-es5 (CLI) flag to true if you want ES5 code.

  • 独立代码可以在 CJS (module.export & require) 或 ESM (exports & import) 中生成,默认为 CJS。如果你想要 ESM 导出代码,请将 code.esm (JS) 值设置为 true 或传递 --code-esm (CLI) 标志。

    ¥The standalone code can be generated in either CJS (module.export & require) or ESM (exports & import), it defaults to CJS. Set the code.esm (JS) value to true or pass the --code-esm (CLI) flag if you want ESM exported code.

请注意,如果你导出单个或多个结构,导出函数的方式会有所不同。请参阅下面的示例。

¥Note that the way the function is exported, differs if you are exporting a single or multiple schemas. See examples below.

# 使用 CLI 生成函数

¥Generating function(s) using CLI

在大多数情况下,你将通过 ajv-cli (opens new window) (>= 4.0.0) 使用此功能来生成导出校验函数的独立代码。请参阅 ajv-cli (opens new window) 文档和 命令行选项 (opens new window) 了解更多信息。

¥In most cases you would use this functionality via ajv-cli (opens new window) (>= 4.0.0) to generate the standalone code that exports the validation function. See ajv-cli (opens new window) docs and the cli options (opens new window) for additional information.

# 使用默认值 - ES6 和 CJS 导出

¥Using the defaults - ES6 and CJS exports

npm install -g ajv-cli
ajv compile -s schema.json -o validate_schema.js

# 使用 JS 库生成

¥Generating using the JS library

安装软件包,版本 >= v7.0.0:

¥Install the package, version >= v7.0.0:

npm install ajv

# 使用 JS 库为单个模式生成函数 - ES6 和 CJS 导出

¥Generating functions(s) for a single schema using the JS library - ES6 and CJS exports

const fs = require("fs")
const path = require("path")
const Ajv = require("ajv")
const standaloneCode = require("ajv/dist/standalone").default

const schema = {
  $id: "https://example.com/bar.json",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    bar: {type: "string"},
  },
  "required": ["bar"]
}

// The generated code will have a default export:
// `module.exports = <validateFunctionCode>;module.exports.default = <validateFunctionCode>;`
const ajv = new Ajv({code: {source: true}})
const validate = ajv.compile(schema)
let moduleCode = standaloneCode(ajv, validate)

// Now you can write the module code to file
fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode)

# 使用 JS 库为多个模式生成函数 - ES6 和 CJS 导出

¥Generating functions(s) for multiple schemas using the JS library - ES6 and CJS exports

const fs = require("fs")
const path = require("path")
const Ajv = require("ajv")
const standaloneCode = require("ajv/dist/standalone").default

const schemaFoo = {
  $id: "#/definitions/Foo",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    foo: {"$ref": "#/definitions/Bar"}
  }
}
const schemaBar = {
  $id: "#/definitions/Bar",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    bar: {type: "string"},
  },
  "required": ["bar"]
}

// For CJS, it generates an exports array, will generate
// `exports["#/definitions/Foo"] = ...;exports["#/definitions/Bar"] = ... ;`
const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true}})
let moduleCode = standaloneCode(ajv)

// Now you can write the module code to file
fs.writeFileSync(path.join(__dirname, "../consume/validate-cjs.js"), moduleCode)

# 使用 JS 库为多个模式生成函数 - ES6 和 ESM 导出

¥Generating functions(s) for multiple schemas using the JS library - ES6 and ESM exports

const fs = require("fs")
const path = require("path")
const Ajv = require("ajv")
const standaloneCode = require("ajv/dist/standalone").default

const schemaFoo = {
  $id: "#/definitions/Foo",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    foo: {"$ref": "#/definitions/Bar"}
  }
}
const schemaBar = {
  $id: "#/definitions/Bar",
  $schema: "http://json-schema.org/draft-07/schema#",
  type: "object",
  properties: {
    bar: {type: "string"},
  },
  "required": ["bar"]
}

// For ESM, the export name needs to be a valid export name, it can not be `export const #/definitions/Foo = ...;` so we
// need to provide a mapping between a valid name and the $id field. Below will generate
// `export const Foo = ...;export const Bar = ...;`
// This mapping would not have been needed if the `$ids` was just `Bar` and `Foo` instead of `#/definitions/Foo`
// and `#/definitions/Bar` respectfully
const ajv = new Ajv({schemas: [schemaFoo, schemaBar], code: {source: true, esm: true}})
let moduleCode = standaloneCode(ajv, {
  "Foo": "#/definitions/Foo",
  "Bar": "#/definitions/Bar"
})

// Now you can write the module code to file
fs.writeFileSync(path.join(__dirname, "../consume/validate-esm.mjs"), moduleCode)

ESM 名称映射

仅当 id 不是有效的导出名称时,ESM 版本才需要映射。如果 $ids 只是 FooBar 而不是 #/definitions/Foo#/definitions/Bar,则不需要映射。

¥The ESM version only requires the mapping if the ids are not valid export names. If the $ids were just the Foo and Bar instead of #/definitions/Foo and #/definitions/Bar then the mapping would not be needed.

# 使用校验函数

¥Using the validation function(s)

# 使用 JS 库校验单个模式 - ES6 和 CJS

¥Validating a single schemas using the JS library - ES6 and CJS

const Bar = require('./validate-cjs')

const barPass = {
    bar: "something"
}

const barFail = {
    // bar: "something" // <= empty/omitted property that is required
}

let validateBar = Bar
if (!validateBar(barPass))
  console.log("ERRORS 1:", validateBar.errors) //Never reaches this because valid

if (!validateBar(barFail))
  console.log("ERRORS 2:", validateBar.errors) //Errors array gets logged

# 使用 JS 库校验多个模式 - ES6 和 CJS

¥Validating multiple schemas using the JS library - ES6 and CJS

const validations = require('./validate-cjs')

const fooPass = {
  foo: {
    bar: "something"
  }
}

const fooFail = {
  foo: {
    // bar: "something" // <= empty/omitted property that is required
  }
}

let validateFoo = validations["#/definitions/Foo"];
if (!validateFoo(fooPass))
  console.log("ERRORS 1:", validateFoo.errors); //Never reaches this because valid

if (!validateFoo(fooFail))
  console.log("ERRORS 2:", validateFoo.errors); //Errors array gets logged

# 使用 JS 库校验多个模式 - ES6 和 ESM

¥Validating multiple schemas using the JS library - ES6 and ESM

import {Foo, Bar} from './validate-multiple-esm.mjs';

const fooPass = {
  foo: {
    bar: "something"
  }
}

const fooFail = {
  foo: {
    // bar: "something" // bar: "something" <= empty properties
  }
}

let validateFoo = Foo;
if (!validateFoo(fooPass))
  console.log("ERRORS 1:", validateFoo.errors); //Never reaches here because valid

if (!validateFoo(fooFail))
  console.log("ERRORS 2:", validateFoo.errors); //Errors array gets logged

# 运行时要求

¥Requirement at runtime

使用独立结构的主要原因之一是更快地启动应用以避免运行时结构编译。

¥One of the main reason for using the standalone mode is to start applications faster to avoid runtime schema compilation.

独立生成的函数仍然依赖于 Ajv。具体是在包的 runtime (opens new window) 文件夹中的代码。

¥The standalone generated functions still has a dependency on the Ajv. Specifically on the code in the runtime (opens new window) folder of the package.

如果需要,可以生成完全隔离的校验函数(不适用于大多数用例)。通过 ES Build 等打包器运行生成的代码,以创建完全隔离的校验函数,可以导入/需要这些函数,而不依赖于 Ajv。如果你的项目已经使用打包器,则也不需要这样做。

¥Completely isolated validation functions can be generated if desired (won't be for most use cases). Run the generated code through a bundler like ES Build to create completely isolated validation functions that can be imported/required without any dependency on Ajv. This is also not needed if your project is already using a bundler.

# 配置和限制

¥Configuration and limitations

支持独立代码生成:

¥To support standalone code generation:

  • Ajv 选项 code.source 必须设置为 true

    ¥Ajv option code.source must be set to true

  • 仅支持 codemacro 用户定义关键字(参见 用户定义的关键字)。

    ¥only code and macro user-defined keywords are supported (see User defined keywords).

  • code 关键字使用 gen.scopeValue 在共享作用域中定义变量时,它们必须随代码片段提供 code 属性。预定义关键字的示例参见 词汇文件夹 (opens new window) 中的源代码。

    ¥when code keywords define variables in shared scope using gen.scopeValue, they must provide code property with the code snippet. See source code of pre-defined keywords for examples in vocabularies folder (opens new window).

  • 如果在独立代码中使用格式,ajv 选项 code.formats 应包含代码片段,该代码片段将计算为具有所有使用的格式定义的对象 - 它可以是使用正确路径(相对于保存模块的位置)调用 require("...")

    ¥if formats are used in standalone code, ajv option code.formats should contain the code snippet that will evaluate to an object with all used format definitions - it can be a call to require("...") with the correct path (relative to the location of saved module):

import myFormats from "./my-formats"
import Ajv, {_} from "ajv"
const ajv = new Ajv({
  formats: myFormats,
  code: {
    source: true,
    formats: _`require("./my-formats")`,
  },
})

If you only use formats from [ajv-formats](https://github.com/ajv-validator/ajv-formats) this option will be set by this package automatically.