Паттерны ключей позволяют описывать правила для динамических свойств объекта. Вместо перечисления каждого поля вручную можно определить регулярное выражение, которому должны соответствовать имена ключей.
Механизм особенно полезен в случаях:
Основной инструмент — метод object.pattern().
object.pattern()Базовый синтаксис:
Joi.object().pattern(pattern, schema)
Аргументы:
| Аргумент | Назначение |
|---|---|
pattern |
Регулярное выражение или схема Joi |
schema |
Правила валидации значений |
Пример объекта с неизвестными заранее ключами:
const Joi = require('joi');
const schema = Joi.object().pattern(
/^user_/,
Joi.string()
);
const data = {
user_name: 'Alex',
user_city: 'London'
};
const result = schema.validate(data);
console.log(result.error);
Правило означает:
user_,const data = {
user_name: 'Alex',
user_age: 25
};
Ошибка:
"user_age" must be a string
Хотя ключ соответствует паттерну, значение нарушает правило
Joi.string().
Регулярное выражение /.*/ позволяет применять правило ко
всем свойствам.
const schema = Joi.object().pattern(
/.*/,
Joi.number()
);
Допустимые данные:
{
width: 100,
height: 200,
depth: 50
}
Недопустимые:
{
width: 100,
title: 'Box'
}
Паттерны часто используются для контроля структуры имени поля.
const schema = Joi.object().pattern(
/^[0-9]+$/,
Joi.string()
);
Допустимо:
{
1: 'one',
2: 'two'
}
Недопустимо:
{
abc: 'text'
}
const schema = Joi.object().pattern(
/^[0-9a-fA-F-]{36}$/,
Joi.boolean()
);
Пример:
{
'550e8400-e29b-41d4-a716-446655440000': true
}
const schema = Joi.object().pattern(
/^[a-z]{2}$/,
Joi.string()
);
Подходящий объект:
{
en: 'Hello',
fr: 'Bonjour',
de: 'Hallo'
}
Паттерн может описывать не только примитивы.
const schema = Joi.object().pattern(
/^product_/,
Joi.object({
title: Joi.string().required(),
price: Joi.number().positive().required()
})
);
Данные:
{
product_1: {
title: 'Phone',
price: 500
},
product_2: {
title: 'Laptop',
price: 1500
}
}
keys() и
pattern()Часто часть полей известна заранее, а часть — динамическая.
const schema = Joi.object({
id: Joi.number().required(),
createdAt: Joi.date()
}).pattern(
/^meta_/,
Joi.string()
);
Допустимые данные:
{
id: 1,
createdAt: '2025-01-01',
meta_author: 'Admin',
meta_source: 'API'
}
По умолчанию объект может содержать дополнительные свойства.
Для полного контроля используется .unknown(false).
const schema = Joi.object({
id: Joi.number()
})
.pattern(/^meta_/, Joi.string())
.unknown(false);
Допустимо:
{
id: 1,
meta_author: 'Admin'
}
Недопустимо:
{
id: 1,
random: true
}
Ошибка:
"random" is not allowed
В объекте можно определять несколько правил.
const schema = Joi.object()
.pattern(/^str_/, Joi.string())
.pattern(/^num_/, Joi.number())
.pattern(/^bool_/, Joi.boolean());
Пример:
{
str_name: 'Alex',
num_age: 30,
bool_admin: true
}
Если ключ соответствует нескольким паттернам, Joi применяет все правила.
const schema = Joi.object()
.pattern(/^item_/, Joi.object())
.pattern(/_admin$/, Joi.boolean());
Проблемный пример:
{
item_admin: true
}
Ключ:
^item__admin$Следовательно:
Это создаёт конфликт.
matchesМетод matches позволяет валидировать количество
совпадений.
const schema = Joi.object().pattern(
/^feature_/,
Joi.boolean(),
{
matches: Joi.array().min(2)
}
);
Требуется минимум два ключа:
{
feature_chat: true,
feature_upload: false
}
const schema = Joi.object().pattern(
/^tag_/,
Joi.string(),
{
matches: Joi.array().max(3)
}
);
const schema = Joi.object().pattern(
/^phone_/,
Joi.string(),
{
matches: Joi.array().length(2)
}
);
Объект обязан содержать ровно два ключа phone_*.
Вместо RegExp допускается схема Joi.
const schema = Joi.object().pattern(
Joi.string().min(3),
Joi.number()
);
Правило:
Паттерны могут использоваться рекурсивно.
const schema = Joi.object({
settings: Joi.object().pattern(
/^module_/,
Joi.object({
enabled: Joi.boolean(),
version: Joi.string()
})
)
});
Данные:
{
settings: {
module_auth: {
enabled: true,
version: '1.0'
},
module_payments: {
enabled: false,
version: '2.1'
}
}
}
Один из наиболее распространённых сценариев.
const schema = Joi.object({
NODE_ENV: Joi.string()
})
.pattern(
/^APP_/,
Joi.string().required()
);
Подходящий объект:
{
NODE_ENV: 'production',
APP_PORT: '3000',
APP_SECRET: 'token'
}
const schema = Joi.object().pattern(
/^[a-z0-9_.-]+$/,
Joi.alternatives().try(
Joi.string(),
Joi.number(),
Joi.boolean()
)
);
Допустимые ключи:
{
'ui.theme': 'dark',
'server.port': 8080,
'cache.enabled': true
}
const schema = Joi.object().pattern(
/^list_/,
Joi.array().items(Joi.string())
);
const schema = Joi.object().pattern(
/^date_/,
Joi.date()
);
const schema = Joi.object().pattern(
/^amount_/,
Joi.number().positive()
);
alternatives()Паттерны хорошо сочетаются с альтернативными схемами.
const schema = Joi.object().pattern(
/^config_/,
Joi.alternatives().try(
Joi.string(),
Joi.number(),
Joi.boolean()
)
);
const schema = Joi.object().pattern(
/^user_/,
Joi.string()
);
const result = schema.validate({
admin: true
});
Если unknown(false) не используется, ошибка не
возникнет, потому что правило применяется только к совпадающим
ключам.
unknown(false)const schema = Joi.object()
.pattern(/^user_/, Joi.string())
.unknown(false);
Теперь:
{
admin: true
}
вызовет:
"admin" is not allowed
Явно определённые поля имеют более высокий приоритет.
const schema = Joi.object({
user_id: Joi.number()
}).pattern(
/^user_/,
Joi.string()
);
Поле:
{
user_id: 5
}
валидно, несмотря на паттерн Joi.string().
.required()Паттерны сами по себе не делают ключи обязательными.
const schema = Joi.object().pattern(
/^prop_/,
Joi.string()
);
Такой объект валиден:
{}
Для обязательного наличия используются matches.
const schema = Joi.object().pattern(
/^prop_/,
Joi.string(),
{
matches: Joi.array().min(1)
}
);
const schema = Joi.object().pattern(
/^[a-z]{3,10}$/,
Joi.number()
);
const schema = Joi.object().pattern(
/^[A-Z_]+$/,
Joi.string()
);
const schema = Joi.object().pattern(
/^[a-z_]+$/,
Joi.string()
);
const schema = Joi.object().pattern(
/^item_[0-9]+$/,
Joi.object({
value: Joi.string().required()
})
);
Пример:
{
item_1: { value: 'A' },
item_2: { value: 'B' }
}
const schema = Joi.object({
translations: Joi.object().pattern(
/^[a-z]{2}$/,
Joi.object({
title: Joi.string(),
description: Joi.string()
})
)
});
/.*/
Такое выражение перехватывает все ключи и может конфликтовать с другими правилами.
unknown(false)Паттерн не запрещает остальные свойства автоматически.
Joi.object()
.pattern(/^cfg_/, Joi.string());
Объект:
{
random: true
}
будет валиден.
.pattern(/^a/, Joi.string())
.pattern(/z$/, Joi.number())
Ключ:
abz
должен одновременно быть:
const schema = Joi.object({
appName: Joi.string().required(),
port: Joi.number().required()
})
.pattern(
/^feature_/,
Joi.boolean()
)
.pattern(
/^env_/,
Joi.string()
)
.unknown(false);
Допустимые данные:
{
appName: 'MyApp',
port: 3000,
feature_auth: true,
feature_cache: false,
env_mode: 'production',
env_region: 'eu'
}
Недопустимые:
{
appName: 'MyApp',
port: 3000,
random: 123
}
Ошибка:
"random" is not allowed