# 用户定义的关键字

¥User defined keywords

# 关键字定义的公共属性

¥Common attributes of keyword definitions

定义所有关键字的常用接口具有以下属性:

¥The usual interface to define all keywords has these properties:

interface _KeywordDef {
  keyword: string | string[]
  type?: JSONType | JSONType[] // data type(s) that keyword applies to,
  // if defined, it is usually "string", "number", "object" or "array"
  schemaType?: JSONType | JSONType[] // the allowed type(s) of value that keyword must have in the schema
  error?: {
    message: string | ((cxt: KeywordCxt) => Code)
    params?: (cxt: KeywordCxt) => Code
  }
}

关键字定义可能有其他可选属性 - see types (opens new window) and KeywordCxt (opens new window)

¥Keyword definitions may have additional optional properties - see types (opens new window) and KeywordCxt (opens new window).

# 使用代码生成功能定义关键字

¥Define keyword with code generation function

recommended

从 v7 开始,Ajv 对所有预定义关键字使用 代码生成模块 (opens new window) - see codegen.md for details.

¥Starting from v7 Ajv uses CodeGen module (opens new window) for all pre-defined keywords - see codegen.md for details.

这是用户定义关键字的最佳方法:

¥This is the best approach for user defined keywords:

  • 安全防止代码注入

    ¥safe against code injection

  • 最棒的表演

    ¥best performance

  • 对校验过程的精确控制

    ¥the precise control over validation process

  • 访问父数据以及当前校验数据的路径

    ¥access to the parent data and the path to the currently validated data

虽然 Ajv 可以安全地与纯 JavaScript 一起使用,但强烈建议对生成代码的用户定义关键字使用 Typescript - 防止通过不受信任的模式注入代码部分基于类型系统,而不仅仅是运行时检查。

¥While Ajv can be safely used with plain JavaScript, it is strongly recommended to use Typescript for user-defined keywords that generate code - the prevention against code injection via untrusted schemas is partially based on the type system, not only on runtime checks.

关键字生成代码的常用关键字定义使用 "code" 函数扩展公共接口:

¥The usual keyword definition for keywords generating code extends common interface with "code" function:

interface CodeKeywordDefinition extends _KeywordDef {
  code: (cxt: KeywordCxt, ruleType?: string) => void // code generation function
}

even 关键字示例:

¥Example even keyword:

import {_, KeywordCxt} from Ajv

ajv.addKeyword({
  keyword: "even",
  type: "number",
  schemaType: "boolean",
  // $data: true // to support [$data reference](./guide/combining-schemas.md#data-reference), ...
  code(cxt: KeywordCxt) {
    const {data, schema} = cxt
    const op = schema ? _`!==` : _`===`
    cxt.fail(_`${data} %2 ${op} 0`) // ... the only code change needed is to use `cxt.fail$data` here
  },
})

const schema = {even: true}
const validate = ajv.compile(schema)
console.log(validate(2)) // true
console.log(validate(3)) // false

range 关键字示例:

¥Example range keyword:

import {_, nil, KeywordCxt} from Ajv

ajv.addKeyword({
  keyword: "range",
  type: "number",
  code(cxt: KeywordCxt) {
    const {schema, parentSchema, data} = cxt
    const [min, max] = schema
    const eq: Code = parentSchema.exclusiveRange ? _`=` : nil
    cxt.fail(_`${data} <${eq} ${min} || ${data} >${eq} ${max}`)
  },
  metaSchema: {
    type: "array",
    items: [{type: "number"}, {type: "number"}],
    minItems: 2,
    additionalItems: false,
  },
})

你可以查看 validation (opens new window) 文件夹中预定义的 Ajv 关键字以获取更高级的示例 - 与之前版本的 Ajv 相比,定义代码生成关键字要容易得多。

¥You can review pre-defined Ajv keywords in validation (opens new window) folder for more advanced examples - it is much easier to define code generation keywords than it was in the previous version of Ajv.

有关可在关键字中使用的属性的更多信息,请参阅 KeywordCxt (opens new window)SchemaCxt (opens new window) 类型定义。

¥See KeywordCxt (opens new window) and SchemaCxt (opens new window) type definitions for more information about properties you can use in your keywords.

# 用 "validate" 函数定义关键字

¥Define keyword with "validate" function

校验关键字的常用关键字定义:

¥Usual keyword definition for validation keywords:

interface FuncKeywordDefinition extends _KeywordDef {
  validate?: SchemaValidateFunction | DataValidateFunction // DataValidateFunction requires `schema: false` option
  schema?: boolean // schema: false makes validate not to expect schema (DataValidateFunction)
  modifying?: boolean
  async?: boolean
  valid?: boolean
  errors?: boolean | "full"
}

interface SchemaValidateFunction {
  (schema: any, data: any, parentSchema?: AnySchemaObject, dataCxt?: DataValidationCxt):
    | boolean
    | Promise<any>
  errors?: Partial<ErrorObject>[]
}

interface DataValidateFunction {
  (this: Ajv | any, data: any, dataCxt?: DataValidationCxt): boolean | Promise<any>
  errors?: Partial<ErrorObject>[]
}

该函数应返回布尔值形式的校验结果。它可以通过自身的 .errors 属性返回一组校验错误(否则将使用标准错误)。

¥The function should return validation result as boolean. It can return an array of validation errors via .errors property of itself (otherwise a standard error will be used).

validate 关键字适用于:

¥validate keywords are suitable for:

  • 在将关键字转换为编译/代码关键字之前测试你的关键字

    ¥testing your keywords before converting them to compiled/code keywords

  • 定义不依赖于结构值的关键字(例如,当值始终为 true 时)。在这种情况下,你可以将选项 schema: false 添加到关键字定义中,并且结构不会传递给校验函数,它只会接收与编译校验函数相同的参数。

    ¥defining keywords that do not depend on the schema value (e.g., when the value is always true). In this case you can add option schema: false to the keyword definition and the schemas won't be passed to the validation function, it will only receive the same parameters as compiled validation function.

  • 定义关键字,其中结构是某些表达式中使用的值。

    ¥defining keywords where the schema is a value used in some expression.

  • 定义支持 $data 参考 的关键字 - 在这种情况下,需要 validatecode 功能,作为唯一的选项或作为 compilemacro 的补充。

    ¥defining keywords that support $data reference - in this case validate or code function is required, either as the only option or in addition to compile or macro.

示例:constant 关键字(06 草案关键字 const 的同义词,相当于某一项的 enum 关键字):

¥Example: constant keyword (a synonym for draft-06 keyword const, it is equivalent to enum keyword with one item):

ajv.addKeyword({
  keyword: "constant",
  validate: (schema, data) =>
    typeof schema == "object" && schema !== null ? deepEqual(schema, data) : schema === data,
  errors: false,
})

const schema = {
  constant: 2,
}
const validate = ajv.compile(schema)
console.log(validate(2)) // true
console.log(validate(3)) // false

const schema = {
  constant: {foo: "bar"},
}
const validate = ajv.compile(schema)
console.log(validate({foo: "bar"})) // true
console.log(validate({foo: "baz"})) // false

const 关键字已在 Ajv 中可用。

¥const keyword is already available in Ajv.

不定义错误的关键字

如果关键字没有定义错误(参见 报告错误),则在其定义中传递 errors: false;它将使生成的代码更加高效。

¥If the keyword does not define errors (see Reporting errors) pass errors: false in its definition; it will make generated code more efficient.

要添加异步关键字,请在其定义中传递 async: true

¥To add asynchronous keyword pass async: true in its definition.

# 用 "compile" 函数定义关键字

¥Define keyword with "compile" function

该关键字与 "validate" 类似,不同之处在于 "compile" 属性具有将在结构编译期间调用的函数,并且应返回校验函数:

¥The keyword is similar to "validate", with the difference that "compile" property has function that will be called during schema compilation and should return validation function:

interface FuncKeywordDefinition extends _KeywordDef {
  compile?: (schema: any, parentSchema: AnySchemaObject, it: SchemaObjCxt) => DataValidateFunction
  schema?: boolean // schema: false makes validate not to expect schema (DataValidateFunction)
  modifying?: boolean
  async?: boolean
  valid?: boolean
  errors?: boolean | "full"
}

在某些情况下,这是定义关键字的最佳方法,但它在校验期间会产生额外函数调用的性能成本。如果关键字逻辑可以通过其他 JSON 结构来表达,那么 macro 关键字定义会更有效(见下文)。

¥In some cases it is the best approach to define keywords, but it has the performance cost of an extra function call during validation. If keyword logic can be expressed via some other JSON Schema then macro keyword definition is more efficient (see below).

例子。使用编译结构的 rangeexclusiveRange 关键字:

¥Example. range and exclusiveRange keywords using compiled schema:

ajv.addKeyword({
  keyword: "range",
  type: "number",
  compile([min, max], parentSchema) {
    return parentSchema.exclusiveRange === true
      ? (data) => data > min && data < max
      : (data) => data >= min && data <= max
  },
  errors: false,
  metaSchema: {
    // schema to validate keyword value
    type: "array",
    items: [{type: "number"}, {type: "number"}],
    minItems: 2,
    additionalItems: false,
  },
})

const schema = {
  range: [2, 4],
  exclusiveRange: true,
}
const validate = ajv.compile(schema)
console.log(validate(2.01)) // true
console.log(validate(3.99)) // true
console.log(validate(2)) // false
console.log(validate(4)) // false

请参阅上一节中有关错误和异步关键字的注释。

¥See note on errors and asynchronous keywords in the previous section.

# 用 "macro" 函数定义关键字

¥Define keyword with "macro" function

关键字定义:

¥Keyword definition:

interface MacroKeywordDefinition extends FuncKeywordDefinition {
  macro: (schema: any, parentSchema: AnySchemaObject, it: SchemaCxt) => AnySchema
}

"Macro" 函数在结构编译期间被调用。它传递了结构、父结构和 结构编译上下文,并且除了原始结构之外,它还应该返回另一个将应用于数据的结构。

¥"Macro" function is called during schema compilation. It is passed schema, parent schema and schema compilation context and it should return another schema that will be applied to the data in addition to the original schema.

这是一种有效的方法(在关键字逻辑可以用另一个 JSON Schema 表达的情况下),因为它通常很容易实现,并且在校验期间没有额外的函数调用。

¥It is an efficient approach (in cases when the keyword logic can be expressed with another JSON Schema), because it is usually easy to implement and there is no extra function call during validation.

除了扩展结构中的错误之外,宏关键字还会添加自己的错误,以防校验失败。

¥In addition to the errors from the expanded schema macro keyword will add its own error in case validation fails.

例子。上一个示例中的 rangeexclusiveRange 关键字是用宏定义的:

¥Example. range and exclusiveRange keywords from the previous example defined with macro:

ajv.addKeyword({
  keyword: "range",
  type: "number",
  macro: ([minimum, maximum]) => ({minimum, maximum}), // schema with keywords minimum and maximum
  // metaSchema: the same as in the example above
})

宏关键字可以递归 - 即返回包含相同关键字的模式。请参见在 test (opens new window) 中定义递归宏关键字 deepProperties 的示例。

¥Macro keywords can be recursive - i.e. return schemas containing the same keyword. See the example of defining a recursive macro keyword deepProperties in the test (opens new window).

# 结构编译上下文

¥Schema compilation context

结构编译上下文 SchemaCxt (opens new window)KeywordCxt (opens new window) 的属性 it 中可用(它也是 compilemacro 关键字函数的第三个参数)。有关可以在此对象中使用的属性,请参阅源代码中的类型。

¥Schema compilation context SchemaCxt (opens new window) is available in property it of KeywordCxt (opens new window) (and it is also the 3rd parameter of compile and macro keyword functions). See types in the source code on the properties you can use in this object.

# 校验时间变量

¥Validation time variables

校验期间可用的所有函数作用域变量均在 names (opens new window).h 中定义。

¥All function scoped variables available during validation are defined in names (opens new window).

# 报告错误

¥Reporting errors

所有关键字都可以定义错误消息,并将 KeywordErrorDefinition 对象作为关键字定义的 error 属性传递:

¥All keywords can define error messages with KeywordErrorDefinition object passed as error property of keyword definition:

interface KeywordErrorDefinition {
  message: string | ((cxt: KeywordErrorCxt) => Code)
  params?: (cxt: KeywordErrorCxt) => Code
}

code 关键字可以通过 cxt.setParams 向这些函数传递参数(参见预定义关键字的实现),其他关键字只能通过这种方式设置字符串消息。

¥code keywords can pass parameters to these functions via cxt.setParams (see implementations of pre-defined keywords), other keywords can only set a string message this way.

另一种报告错误的方法可用于 validatecompile 关键字 - 他们可以通过将错误分配给校验函数的 .errors 属性来定义错误。异步关键字可以返回用 new Ajv.ValidationError(errors) 拒绝的 Promise,其中 errors 是校验错误数组(如果你不想在异步关键字中创建错误,其校验函数可以返回用 false 解析的 Promise)。

¥Another approach for reporting errors can be used for validate and compile keyword - they can define errors by assigning them to .errors property of the validation function. Asynchronous keywords can return promise that rejects with new Ajv.ValidationError(errors), where errors is an array of validation errors (if you don't want to create errors in asynchronous keyword, its validation function can return the promise that resolves with false).

errors 数组中的每个错误对象至少应具有属性 keywordmessageparams,其他属性将被添加。

¥Each error object in errors array should at least have properties keyword, message and params, other properties will be added.

如果关键字未定义或返回错误,则将创建默认错误,以防关键字校验失败。

¥If keyword doesn't define or return errors, the default error will be created in case the keyword fails validation.