Валидация объектов — центральная часть библиотеки Joi. Именно объектные схемы позволяют описывать структуру данных API, конфигураций, DTO, форм, параметров запросов и сложных вложенных сущностей.
Базовая схема объекта создаётся через Joi.object():
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string(),
age: Joi.number()
});
Схема описывает:
const schema = Joi.object({
name: Joi.string().required(),
age: Joi.number().integer()
});
Проверка:
const result = schema.validate({
name: 'Alex',
age: 25
});
console.log(result.error);
const schema = Joi.object({
login: Joi.string().required(),
password: Joi.string().required()
});
Если поле отсутствует:
{
password: '123456'
}
Ошибка:
"login" is required
Все поля по умолчанию необязательны, но optional()
иногда используют для явного указания поведения.
const schema = Joi.object({
middleName: Joi.string().optional()
});
Поле запрещено.
const schema = Joi.object({
role: Joi.forbidden()
});
По умолчанию Joi запрещает поля, не описанные в схеме.
const schema = Joi.object({
name: Joi.string()
});
const result = schema.validate({
name: 'Max',
city: 'London'
});
Ошибка:
"city" is not allowed
const schema = Joi.object({
name: Joi.string()
}).unknown(true);
Теперь дополнительные поля разрешены.
Явный запрет:
const schema = Joi.object({
name: Joi.string()
}).unknown(false);
const schema = Joi.object({
user: Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required()
}).required()
});
Проверка:
const result = schema.validate({
user: {
name: 'John',
email: 'john@mail.com'
}
});
const schema = Joi.object({
company: Joi.object({
address: Joi.object({
city: Joi.string(),
street: Joi.string(),
building: Joi.number()
})
})
});
Очень распространённый сценарий при работе с API.
const schema = Joi.object({
users: Joi.array().items(
Joi.object({
id: Joi.number().required(),
name: Joi.string().required()
})
)
});
Проверка:
const data = {
users: [
{ id: 1, name: 'Alex' },
{ id: 2, name: 'Kate' }
]
};
const schema = Joi.object({
role: Joi.string().default('user')
});
Проверка:
const result = schema.validate({});
Результат:
{
role: 'user'
}
const schema = Joi.object({
name: Joi.string()
}).rename('username', 'name');
Проверка:
const result = schema.validate({
username: 'Alex'
});
Результат:
{
name: 'Alex'
}
Удаляет поле из результата после успешной проверки.
const schema = Joi.object({
username: Joi.string(),
password: Joi.string().strip()
});
Проверка:
const result = schema.validate({
username: 'admin',
password: 'secret'
});
Результат:
{
username: 'admin'
}
Объектные схемы особенно мощны благодаря поддержке логических зависимостей.
Поле требует присутствия другого поля.
const schema = Joi.object({
password: Joi.string(),
repeatPassword: Joi.string()
}).with('password', 'repeatPassword');
Если есть password, то должен быть и
repeatPassword.
Запрещает совместное использование.
const schema = Joi.object({
token: Joi.string(),
password: Joi.string()
}).without('token', 'password');
Все поля должны существовать одновременно.
const schema = Joi.object({
latitude: Joi.number(),
longitude: Joi.number()
}).and('latitude', 'longitude');
Хотя бы одно поле обязательно.
const schema = Joi.object({
phone: Joi.string(),
email: Joi.string()
}).or('phone', 'email');
Разрешено только одно поле.
const schema = Joi.object({
id: Joi.number(),
uuid: Joi.string()
}).xor('id', 'uuid');
Не более одного поля.
const schema = Joi.object({
email: Joi.string(),
telegram: Joi.string()
}).oxor('email', 'telegram');
Запрещённая комбинация.
const schema = Joi.object({
startDate: Joi.date(),
archived: Joi.boolean()
}).nand('startDate', 'archived');
Проверка пользовательских зависимостей.
const schema = Joi.object({
min: Joi.number(),
max: Joi.number()
}).assert(
'.max',
Joi.number().greater(Joi.ref('min')),
'max must be greater than min'
);
const schema = Joi.object({
password: Joi.string().required(),
repeatPassword: Joi.any()
.valid(Joi.ref('password'))
.required()
});
Позволяет валидировать динамические ключи.
const schema = Joi.object().pattern(
/^item_/,
Joi.number()
);
Допустимо:
{
item_1: 10,
item_2: 20
}
Недопустимо:
{
item_1: 'abc'
}
Минимальное число ключей.
const schema = Joi.object({
a: Joi.any(),
b: Joi.any(),
c: Joi.any()
}).min(2);
const schema = Joi.object({
a: Joi.any(),
b: Joi.any(),
c: Joi.any()
}).max(2);
Точное количество ключей.
const schema = Joi.object({
a: Joi.any(),
b: Joi.any()
}).length(2);
Позволяет получить часть схемы.
const schema = Joi.object({
profile: Joi.object({
email: Joi.string().email()
})
});
const emailSchema = schema.extract('profile.email');
Метод возвращает внутреннее представление схемы.
const schema = Joi.object({
name: Joi.string().required()
});
console.log(schema.describe());
Результат содержит:
Позволяет изменить несколько полей.
const schema = Joi.object({
username: Joi.string(),
email: Joi.string(),
password: Joi.string()
});
const updatedSchema = schema.fork(
['username', 'email'],
field => field.required()
);
const schema = Joi.object({
name: Joi.string(),
email: Joi.string()
}).prefs({
presence: 'required'
});
Теперь все поля обязательны.
Крупные объектные схемы обычно разбивают на отдельные части.
const addressSchema = Joi.object({
city: Joi.string(),
street: Joi.string()
});
const userSchema = Joi.object({
name: Joi.string(),
address: addressSchema
});
const baseSchema = Joi.object({
id: Joi.number()
});
const extendedSchema = baseSchema.concat(
Joi.object({
name: Joi.string()
})
);
const schema = Joi.alternatives().try(
Joi.object({
type: Joi.string().valid('user'),
username: Joi.string().required()
}),
Joi.object({
type: Joi.string().valid('admin'),
permissions: Joi.array().required()
})
);
const schema = Joi.object({
type: Joi.string().required(),
value: Joi.when('type', {
is: 'email',
then: Joi.string().email(),
otherwise: Joi.string().min(3)
})
});
Типичный пример для REST API:
const createUserSchema = Joi.object({
username: Joi.string()
.min(3)
.max(30)
.required(),
email: Joi.string()
.email()
.required(),
password: Joi.string()
.min(8)
.required(),
age: Joi.number()
.integer()
.min(18),
roles: Joi.array().items(
Joi.string()
).default(['user'])
});
const configSchema = Joi.object({
port: Joi.number()
.port()
.required(),
host: Joi.string()
.hostname()
.required(),
database: Joi.object({
user: Joi.string().required(),
password: Joi.string().required(),
dbName: Joi.string().required()
}).required()
});
const schema = Joi.object({
start: Joi.date(),
end: Joi.date()
}).custom((value, helpers) => {
if (value.start > value.end) {
return helpers.error('any.invalid');
}
return value;
});
const schema = Joi.object({
username: Joi.string().required()
}).messages({
'any.required': 'Поле обязательно',
'string.base': 'Должна быть строка'
});
По умолчанию проверка прекращается после первой ошибки.
const result = schema.validate(data, {
abortEarly: false
});
Joi умеет автоматически преобразовывать значения.
const schema = Joi.object({
age: Joi.number()
});
Проверка:
schema.validate({
age: '25'
});
Строка будет преобразована в число.
const result = schema.validate(data, {
convert: false
});
Жёсткий режим проверки.
const schema = Joi.object({
age: Joi.number()
}).strict();
Позволяет именовать схемы.
const schema = Joi.object({
name: Joi.string()
}).id('UserSchema');
const schema = Joi.object({
name: Joi.string()
}).meta({
className: 'User'
});
const schema = Joi.object({
email: Joi.string()
.email()
.description('User email')
});
const schema = Joi.object({
name: Joi.string()
}).tag('api');
const orderSchema = Joi.object({
id: Joi.number()
.integer()
.required(),
customer: Joi.object({
name: Joi.string()
.min(2)
.required(),
email: Joi.string()
.email()
.required()
}).required(),
products: Joi.array()
.items(
Joi.object({
productId: Joi.number()
.required(),
quantity: Joi.number()
.integer()
.min(1)
.required()
})
)
.min(1)
.required(),
status: Joi.string()
.valid(
'new',
'paid',
'delivered'
)
.default('new'),
createdAt: Joi.date()
.default(Date.now)
});
Такие схемы используются: