При валидации данных в JavaScript с использованием библиотеки Joi часто возникает необходимость гарантировать, что массив содержит только уникальные элементы. Это особенно важно при работе с идентификаторами, списками тегов, наборами ролей пользователей или любыми структурами данных, где дубликаты нарушают бизнес-логику.
Механизм проверки уникальности реализуется через метод
unique() в описании схемы массива. Он позволяет строго
контролировать повторяющиеся значения и предотвращать попадание
дубликатов в итоговые данные.
unique()Самый простой вариант применения — проверка массива примитивных значений:
const Joi = require('joi');
const schema = Joi.array().items(Joi.number()).unique();
schema.validate([1, 2, 3, 4]); // корректно
schema.validate([1, 2, 2, 4]); // ошибка
В этом случае библиотека сравнивает значения напрямую. Если обнаруживаются одинаковые элементы, валидация завершается ошибкой.
При нарушении уникальности Joi возвращает объект ошибки с кодом:
array.uniqueПример структуры ошибки:
{
message: '"value" contains a duplicate value',
type: 'array.unique',
path: [],
context: {
pos: 2,
value: 2,
dupePos: 1
}
}
Поля pos и dupePos позволяют определить
позиции повторяющихся элементов, что полезно при отладке и
логировании.
При работе с объектами стандартное сравнение не подходит, так как объекты сравниваются по ссылке. Для решения этой задачи используется функция-ключ:
const schema = Joi.array().items(
Joi.object({
id: Joi.number().required(),
name: Joi.string()
})
).unique('id');
schema.validate([
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 1, name: 'C' }
]); // ошибка
Здесь уникальность определяется по полю id. Даже если
остальные поля различаются, совпадение ключа приводит к ошибке.
Для более сложных сценариев применяется функция:
const schema = Joi.array().items(Joi.object({
x: Joi.number(),
y: Joi.number()
})).unique((a, b) => {
return a.x === b.x && a.y === b.y;
});
Функция получает два элемента массива и должна вернуть
true, если элементы считаются одинаковыми. Это позволяет
реализовать произвольную логику сравнения.
Joi поддерживает доступ к вложенным свойствам через строковый путь:
const schema = Joi.array().items(
Joi.object({
user: Joi.object({
id: Joi.number()
})
})
).unique('user.id');
Такой подход удобен при работе с API-ответами или сложными структурами данных, где идентификаторы находятся внутри вложенных объектов.
При проверке уникальности важно учитывать значения null
и undefined. По умолчанию они также участвуют в
сравнении:
Joi.array().unique().validate([null, null]); // ошибка
Joi.array().unique().validate([undefined, undefined]); // ошибка
Если требуется особая обработка таких значений, используется кастомная функция сравнения, где можно явно исключить их из проверки.
Метод unique() может комбинироваться с другими
ограничениями:
min() — минимальная длина массиваmax() — максимальная длина массиваlength() — фиксированная длинаitems() — типизация элементовПример комплексной схемы:
const schema = Joi.array()
.items(Joi.string().min(3))
.min(1)
.max(10)
.unique();
В этом случае сначала проверяется структура и размер массива, затем выполняется проверка на дубликаты.
При использовании строковых путей или функций важно учитывать потенциальные ошибки:
Пример проблемного случая:
Joi.array().unique('id').validate([
{ name: 'A' },
{ id: 1 }
]);
Первый объект не содержит id, что приводит к
неоднозначному сравнению.
При больших массивах проверка уникальности может влиять на производительность. Внутри Joi используется алгоритм сравнения, близкий к O(n²) в худших случаях, особенно при использовании пользовательских функций.
Для оптимизации рекомендуется:
При включённых преобразованиях (convert: true) важно
учитывать, что уникальность проверяется уже после приведения типов:
Joi.array().unique().validate(['1', 1]);
После конвертации оба значения могут стать одинаковыми, что приведёт к ошибке.
При использовании alternatives() внутри массива
уникальность применяется к уже выбранному типу:
const schema = Joi.array().items(
Joi.alternatives().try(
Joi.string(),
Joi.number()
)
).unique();
Здесь Joi сначала определяет тип каждого элемента, затем сравнивает значения в приведённом виде.
При включённом strict режиме отключается автоматическое
преобразование типов, что напрямую влияет на уникальность:
Joi.array().unique().validate(['1', 1], { convert: false });
В этом случае значения считаются различными, поскольку сравнение происходит без приведения типов.
На практике уникальность часто применяется в следующих случаях:
Каждый из этих сценариев требует выбора правильного способа сравнения: примитивного, по ключу или через функцию.
Несмотря на гибкость, механизм имеет ряд ограничений:
Эти ограничения компенсируются возможностью кастомизации через функцию сравнения и комбинацией с другими правилами Joi.