# 在校验期间修改数据

¥Modifying data during validation

# 一般考虑因素

¥General considerations

Ajv 有几个选项允许在校验期间修改数据:

¥Ajv has several options that allow to modify data during validation:

  • removeAdditional - 删除架构对象中未定义的属性。

    ¥removeAdditional - to remove properties not defined in the schema object.

  • useDefaults - 将模式中的默认值分配给已校验的数据属性。

    ¥useDefaults - to assign defaults from the schema to the validated data properties.

  • coerceTypes - 如果可能,更改数据类型以匹配架构中的类型。

    ¥coerceTypes - to change data type, when possible, to match the type(s) in the schema.

你还可以定义修改数据的关键字。

¥You can also define keywords that modify data.

无法修改根数据

无法修改传递给校验函数的根数据实例,只能修改数据属性。这与 JavaScript 传递参数的方式有关,而不是 Ajv 的限制。

¥It is not possible to modify the root data instance passed to the validation function, only data properties can be modified. This is related to how JavaScript passes parameters, and not a limitation of Ajv.

非可移植功能

This functionality is non-standard - 其他 JSON 模式校验器实现可能不支持这一点。

¥This functionality is non-standard - this is likely to be unsupported in other JSON Schema validator implementations.

修改数据时出现意外结果

虽然纯结构校验生成的结果与关键字和子结构顺序无关,但启用任何可能修改数据的功能会使校验变得不纯粹,并且其结果可能取决于关键字和子结构的评估顺序。

¥While pure schema validation produces the results independent of the keywords and subschema order, enabling any feature that may modify the data makes validation impure and its results are likely to depend on the order of evaluation of keywords and subschemas.

诸如 allOf 之类的关键字中子结构的求值顺序始终与数组中子结构的顺序相同。

¥The order of evaluation of subschemas in keywords like allOf is always the same as the order of subschemas in the array.

另一方面,关键字的评估顺序虽然在校验之间一致并且不依赖于模式对象的创建方式,但既没有记录也没有保证,因此它可能会在未来的主要版本中发生变化(并且在极少数情况下,它可能会发生变化) 在次要版本中 - 例如,当存在需要修复的错误时)。

¥On another hand, the order of evaluation of keywords, while consistent between validations and not dependent on how schema object is created, is neither documented nor guaranteed, so it can change in the future major versions (and, in rare cases, it can change in minor version - e.g. when there is bug that needs to be fixed).

强烈建议始终将可以在 allOf 关键字内的单独子结构中改变数据的用户定义关键字放在一起,以使评估顺序明确。此建议的例外情况是预定义的 defaulttype 关键字 - 它们必须与其他关键字保持在相同的架构中。

¥It is strongly recommended to always put user-defined keywords that can mutate data in separate subschemas inside allOf keyword to make the order of evaluation unambiguous. The exceptions to this recommendation are pre-defined default and type keywords - they must remain in the same schema as other keywords.

# 删除附加属性

¥Removing additional properties

使用 选项 removeAdditional(由 andyscott (opens new window) 添加),你可以在校验期间过滤数据。

¥With option removeAdditional (added by andyscott (opens new window)) you can filter data during the validation.

该选项修改原始数据。

¥This option modifies original data.

示例:

¥Example:

const ajv = new Ajv({removeAdditional: true})
const schema = {
  additionalProperties: false,
  properties: {
    foo: {type: "number"},
    bar: {
      additionalProperties: {type: "number"},
      properties: {
        baz: {type: "string"},
      },
    },
  },
}

const data = {
  foo: 0,
  additional1: 1, // will be removed; `additionalProperties` == false
  bar: {
    baz: "abc",
    additional2: 2, // will NOT be removed; `additionalProperties` != false
  },
}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": 0, "bar": { "baz": "abc", "additional2": 2 }

如果上例中的 removeAdditional 选项是 "all",则 additional1additional2 属性都将被删除。

¥If removeAdditional option in the example above were "all" then both additional1 and additional2 properties would have been removed.

如果选项是 "failing",则无论其值如何,属性 additional1 都将被删除,而属性 additional2 仅当其值未通过内部 additionalProperties 中的结构时才会被删除(因此在上面的示例中,它会保留,因为它通过了结构) ,但任何非数字都会被删除)。

¥If the option were "failing" then property additional1 would have been removed regardless of its value and property additional2 would have been removed only if its value were failing the schema in the inner additionalProperties (so in the example above it would have stayed because it passes the schema, but any non-number would have been removed).

将 removeAdditional 与 anyOf/oneOf 一起使用时出现意外结果

如果你在 anyOf/oneOf 关键字内使用 removeAdditional 选项和 additionalProperties 关键字,则此结构的校验可能会失败。为了使其按预期工作,你必须使用带有 discriminator 关键字的可区分联合(需要 discriminator 选项)。

¥If you use removeAdditional option with additionalProperties keyword inside anyOf/oneOf keywords your validation can fail with this schema. To make it work as you expect, you have to use discriminated union with discriminator keyword (requires discriminator option).

例如,通过这种非歧视性的联合,你将获得意想不到的结果:

¥For example, with this non-discriminated union you will have unexpected results:

{
  type: "object",
  oneOf: [
    {
      properties: {
        foo: {type: "string"}
      },
      required: ["foo"],
      additionalProperties: false
    },
    {
      properties: {
        bar: {type: "integer"}
      },
      required: ["bar"],
      additionalProperties: false
    }
  ]
}

上述结构的目的是允许对象具有字符串属性 "foo" 或整数属性 "bar",但不能同时具有这两种属性和任何其他属性。

¥The intention of the schema above is to allow objects with either the string property "foo" or the integer property "bar", but not with both and not with any other properties.

使用选项 removeAdditional: true,对象 { "foo": "abc"} 的校验将通过,但对象 {"bar": 1} 的校验将失败。发生这种情况是因为,当校验 oneOf 中的第一个子结构时,属性 bar 被删除,因为根据标准,它是一个附加属性(因为它不包含在同一结构中的 properties 关键字中)。

¥With the option removeAdditional: true the validation will pass for the object { "foo": "abc"} but will fail for the object {"bar": 1}. It happens because while the first subschema in oneOf is validated, the property bar is removed because it is an additional property according to the standard (because it is not included in properties keyword in the same schema).

虽然此行为是意外的(问题 #129 (opens new window)#134 (opens new window)),但它是正确的。为了获得预期的行为(允许两个对象并删除其他属性),必须以这种方式重构结构:

¥While this behaviour is unexpected (issues #129 (opens new window), #134 (opens new window)), it is correct. To have the expected behaviour (both objects are allowed and additional properties are removed) the schema has to be refactored in this way:

{
  type: "object",
  properties: {
    foo: {type: "string"},
    bar: {type: "integer"}
  },
  additionalProperties: false,
  oneOf: [{required: ["foo"]}, {required: ["bar"]}]
}

上面的模式也更高效 - 它将编译成更快的函数。

¥The schema above is also more efficient - it will compile into a faster function.

对于有区别的联合,你可以使用 discriminator 关键字进行结构(它需要 discriminator: true 选项):

¥For discriminated unions you could schemas with discriminator keyword (it requires discriminator: true option):

{
  type: "object",
  discriminator: {propertyName: "tag"},
  required: ["tag"],
  oneOf: [
    {
      properties: {
        tag: {const: "foo"},
        foo: {type: "string"}
      },
      required: ["foo"],
      additionalProperties: false
    },
    {
      properties: {
        tag: {const: "bar"},
        bar: {type: "integer"}
      },
      required: ["bar"],
      additionalProperties: false
    }
  ]
}

使用此结构,将仅评估 oneOf 中的一个子结构,因此 removeAdditional 选项将按预期工作。

¥With this schema, only one subschema in oneOf will be evaluated, so removeAdditional option will work as expected.

请参阅 discriminator 关键字。

¥See discriminator keyword.

# 分配默认值

¥Assigning defaults

对于 选项 useDefaults,Ajv 会将 propertiesitems 结构(当它是结构数组时)中的 default 关键字的值分配给缺少的属性和项目。

¥With option useDefaults Ajv will assign values from default keyword in the schemas of properties and items (when it is the array of schemas) to the missing properties and items.

使用选项值 "empty" 属性和等于 null""(空字符串)的项目将被视为缺失并分配默认值。

¥With the option value "empty" properties and items equal to null or "" (empty string) will be considered missing and assigned defaults.

该选项修改原始数据。

¥This option modifies original data.

默认值是深度克隆的

默认值作为字面量插入到生成的校验代码中,因此插入数据中的值将是结构中默认值的深度克隆。

¥The default value is inserted in the generated validation code as a literal, so the value inserted in the data will be the deep clone of the default in the schema.

示例 1(default 位于 properties):

¥Example 1 (default in properties):

const ajv = new Ajv({useDefaults: true})
const schema = {
  type: "object",
  properties: {
    foo: {type: "number"},
    bar: {type: "string", default: "baz"},
  },
  required: ["foo", "bar"],
}

const data = {foo: 1}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": 1, "bar": "baz" }

示例 2(default 位于 items):

¥Example 2 (default in items):

const schema = {
  type: "array",
  items: [{type: "number"}, {type: "string", default: "foo"}],
}

const data = [1]

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // [ 1, "foo" ]

使用 useDefaults 选项 default 关键字在结构编译期间用于以下情况时会引发异常:

¥With useDefaults option default keywords throws exception during schema compilation when used in:

  • 不在 propertiesitems 子结构中

    ¥not in properties or items subschemas

  • anyOfoneOfnot 内的结构中(参见 #42 (opens new window)

    ¥in schemas inside anyOf, oneOf and not (see #42 (opens new window))

  • if 结构中

    ¥in if schema

  • 在由用户定义的宏关键字生成的结构中

    ¥in schemas generated by user-defined macro keywords

严格结构选项可以更改这些不受支持的默认值的行为(strict: false 忽略它们,"log" 记录警告)。

¥The strict mode option can change the behaviour for these unsupported defaults (strict: false to ignore them, "log" to log a warning).

参见 严格模式

¥See Strict mode.

默认带有鉴别器关键字

如果使用 discriminator 关键字,则将在 oneOf 内的结构中分配默认值。

¥Defaults will be assigned in schemas inside oneOf in case discriminator keyword is used.

# 强制数据类型

¥Coercing data types

当你校验用户输入时,所有数据属性通常都是字符串。选项 coerceTypes 允许你将数据类型强制为结构 type 关键字中指定的类型,以便通过校验并在之后使用正确键入的数据。

¥When you are validating user inputs all your data properties are usually strings. The option coerceTypes allows you to have your data types coerced to the types specified in your schema type keywords, both to pass the validation and to use the correctly typed data afterwards.

该选项修改原始数据。

¥This option modifies original data.

使用标量值进行类型强制转换

如果将标量值传递给校验函数,其类型将被强制并且将通过校验,但你传递的变量的值不会更新,因为标量是按值传递的。

¥If you pass a scalar value to the validating function its type will be coerced and it will pass the validation, but the value of the variable you pass won't be updated because scalars are passed by value.

示例 1:

¥Example 1:

const ajv = new Ajv({coerceTypes: true})
const schema = {
  type: "object",
  properties: {
    foo: {type: "number"},
    bar: {type: "boolean"},
  },
  required: ["foo", "bar"],
}

const data = {foo: "1", bar: "false"}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": 1, "bar": false }

示例 2(数组强制转换):

¥Example 2 (array coercions):

const ajv = new Ajv({coerceTypes: "array"})
const schema = {
  properties: {
    foo: {type: "array", items: {type: "number"}},
    bar: {type: "boolean"},
  },
}

const data = {foo: "1", bar: ["false"]}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": [1], "bar": false }

正如你从示例中看到的,强制规则与 JavaScript 不同,既可以按预期校验用户输入,也可以使强制可逆(正确校验在 "anyOf" 和其他复合关键字的子结构中定义不同类型的情况)。

¥The coercion rules, as you can see from the example, are different from JavaScript both to validate user input as expected and to have the coercion reversible (to correctly validate cases where different types are defined in subschemas of "anyOf" and other compound keywords).

详情请参见 类型强制规则

¥See Type coercion rules for details.