В спецификации JSON Schema конструкция if-then-else
используется для условной валидации данных. Библиотека Ajv поддерживает
этот механизм начиная с черновика JSON Schema Draft-07.
Конструкция позволяет:
oneOf,
anyOf и allOf.Синтаксис напоминает условные операторы в языках программирования:
if (условие) {
then(правила)
} else {
else(другие_правила)
}
В JSON Schema это выглядит так:
{
"if": {
"properties": {
"type": {
"const": "admin"
}
}
},
"then": {
"required": ["permissions"]
},
"else": {
"required": ["guestToken"]
}
}
ifКлючевое слово if описывает схему-условие.
Если данные проходят проверку по схеме внутри if,
активируется then.
Если данные не проходят проверку по if, активируется
else.
Важно понимать:
if сам по себе не влияет на успешность валидации;if только определяет, какая ветка будет
применяться;then или
else.const schema = {
type: "object",
properties: {
age: { type: "number" },
hasLicense: { type: "boolean" }
},
if: {
properties: {
age: {
minimum: 18
}
}
},
then: {
required: ["hasLicense"]
}
}
const Ajv = require("ajv")
const ajv = new Ajv()
const validate = ajv.compile(schema)
console.log(validate({
age: 20,
hasLicense: true
}))
Результат:
true
console.log(validate({
age: 20
}))
Результат:
false
Ошибки:
console.log(validate.errors)
[
{
instancePath: "",
schemaPath: "#/then/required",
keyword: "required",
params: {
missingProperty: "hasLicense"
},
message: "must have required property 'hasLicense'"
}
]
elseconst schema = {
type: "object",
properties: {
role: { type: "string" },
adminCode: { type: "string" },
guestCode: { type: "string" }
},
if: {
properties: {
role: { const: "admin" }
}
},
then: {
required: ["adminCode"]
},
else: {
required: ["guestCode"]
}
}
validate({
role: "admin",
adminCode: "A-100"
})
Результат:
true
validate({
role: "admin"
})
Результат:
false
Поскольку условие if выполнилось, была активирована
ветка then.
validate({
role: "guest",
guestCode: "G-200"
})
Результат:
true
propertiesРаспространённая ошибка связана с тем, что properties не
требует обязательного наличия поля.
Пример:
if: {
properties: {
role: { const: "admin" }
}
}
Такая схема считается валидной даже при отсутствии
role.
properties проверяет поле только если оно
существует.
Следовательно:
{}
пройдёт условие if.
Для строгой проверки необходимо использовать
required.
if: {
required: ["role"],
properties: {
role: { const: "admin" }
}
}
Теперь отсутствие role приведёт к переходу в ветку
else.
const schema = {
type: "object",
properties: {
paymentType: {
enum: ["card", "cash"]
},
cardNumber: {
type: "string"
}
},
if: {
properties: {
paymentType: {
const: "card"
}
},
required: ["paymentType"]
},
then: {
required: ["cardNumber"]
}
}
allOfif может содержать полноценную JSON Schema.
if: {
allOf: [
{
properties: {
country: {
const: "USA"
}
}
},
{
properties: {
age: {
minimum: 21
}
}
}
]
}
Условие выполнится только если:
USA;patternconst schema = {
type: "object",
properties: {
country: { type: "string" },
phone: { type: "string" }
},
if: {
properties: {
country: {
const: "RU"
}
},
required: ["country"]
},
then: {
properties: {
phone: {
pattern: "^\\+7"
}
}
},
else: {
properties: {
phone: {
pattern: "^\\+1"
}
}
}
}
if-then-else можно вкладывать друг в друга.
const schema = {
type: "object",
properties: {
plan: { type: "string" },
users: { type: "number" }
},
if: {
properties: {
plan: { const: "business" }
}
},
then: {
if: {
properties: {
users: {
minimum: 100
}
}
},
then: {
properties: {
supportLevel: {
const: "premium"
}
},
required: ["supportLevel"]
}
}
}
notconst schema = {
type: "object",
properties: {
role: { type: "string" },
active: { type: "boolean" }
},
if: {
properties: {
role: { const: "banned" }
}
},
then: {
properties: {
active: {
const: false
}
}
}
}
const schema = {
type: "object",
properties: {
type: { type: "string" },
items: {
type: "array"
}
},
if: {
properties: {
type: {
const: "nonEmpty"
}
}
},
then: {
properties: {
items: {
minItems: 1
}
}
}
}
const schema = {
type: "object",
properties: {
mode: { type: "string" },
value: { type: "number" }
},
if: {
properties: {
mode: { const: "positive" }
}
},
then: {
properties: {
value: {
minimum: 1
}
}
},
else: {
properties: {
value: {
maximum: -1
}
}
}
}
const schema = {
type: "object",
properties: {
notificationType: { type: "string" },
contact: { type: "string" }
},
if: {
properties: {
notificationType: {
const: "email"
}
}
},
then: {
properties: {
contact: {
format: "email"
}
}
},
else: {
properties: {
contact: {
pattern: "^\\+"
}
}
}
}
dependentSchemasconst schema = {
type: "object",
properties: {
creditCard: {
type: "string"
},
billingAddress: {
type: "string"
}
},
dependentSchemas: {
creditCard: {
if: {
required: ["creditCard"]
},
then: {
required: ["billingAddress"]
}
}
}
}
Ajv компилирует схемы в JavaScript-функции.
Для if-then-else создаётся оптимизированный код с
условными переходами.
Упрощённо это выглядит так:
if (validateIf(data)) {
validateThen(data)
} else {
validateElse(data)
}
Благодаря компиляции:
Ключевые слова:
ifthenelseбыли официально добавлены в JSON Schema Draft-07.
Для корректной работы необходимо использовать соответствующую версию схемы:
{
$schema: "http://json-schema.org/draft-07/schema#"
}
thenЕсли присутствует только if, никаких ограничений не
применяется.
{
if: {
properties: {
type: {
const: "admin"
}
}
}
}
Такая схема бесполезна, потому что результат if нигде не
используется.
elseЭто наиболее распространённый вариант.
{
if: { ... },
then: { ... }
}
Если условие не выполнится:
ifJSON Schema не поддерживает массив условий:
{
if: [ ... ]
}
Неверно.
Правильный подход — комбинация через:
allOfanyOfoneOf{
allOf: [
{
if: { ... },
then: { ... }
},
{
if: { ... },
then: { ... }
}
]
}
const schema = {
type: "object",
properties: {
accountType: {
enum: ["company", "individual"]
},
companyName: {
type: "string"
},
passport: {
type: "string"
}
},
if: {
properties: {
accountType: {
const: "company"
}
},
required: ["accountType"]
},
then: {
required: ["companyName"]
},
else: {
required: ["passport"]
}
}
const schema = {
type: "object",
properties: {
method: {
enum: ["GET", "POST"]
},
body: {
type: "object"
}
},
if: {
properties: {
method: {
const: "POST"
}
}
},
then: {
required: ["body"]
}
}
required в
ifНеправильно:
if: {
properties: {
type: {
const: "A"
}
}
}
Правильно:
if: {
required: ["type"],
properties: {
type: {
const: "A"
}
}
}
if без
then{
if: { ... }
}
Условие никак не влияет на результат проверки.
Плохо:
then: {
required: ["a"],
minimum: 10
}
minimum не применяется к объекту.
Необходимо соблюдать контекст типов.
Хорошо:
if: {
properties: {
role: {
const: "admin"
}
}
}
Плохо:
if: {
properties: {
role: {
enum: ["admin", "moderator", "editor"]
}
}
}
Чем точнее условие, тем проще сопровождение схемы.
requiredПрактически любое условие должно включать:
required: [...]
Слишком большое количество вложенных if-then-else:
oneOfif-then-elseПодходит для:
oneOfПодходит для:
Ajv предоставляет массив ошибок:
validate.errors
Для анализа сложных условий удобно:
allErrors;verbose;const ajv = new Ajv({
allErrors: true,
verbose: true
})
$ref
внутри thenconst schema = {
definitions: {
adminSchema: {
required: ["permissions"]
}
},
if: {
properties: {
role: {
const: "admin"
}
}
},
then: {
$ref: "#/definitions/adminSchema"
}
}
Это позволяет: