В библиотеке Yup работа с массивами строится через
array()-схему, внутри которой описывается структура
элементов с помощью of():
import * as Yup from 'yup';
const schema = Yup.array().of(
Yup.string()
);
Такое определение гарантирует соответствие типов элементов, но не накладывает ограничений на их уникальность. Повторяющиеся значения допускаются по умолчанию.
Для массивов примитивов (строки, числа, булевы значения) наиболее прямолинейный способ проверки уникальности реализуется через кастомный тест:
const schema = Yup.array()
.of(Yup.string())
.test(
'unique',
'Элементы массива должны быть уникальными',
(value) => {
if (!value) return true;
return new Set(value).size === value.length;
}
);
Использование Set позволяет быстро исключить дубликаты
за счёт хранения только уникальных значений.
Часто требуется учитывать не «сырые» значения, а нормализованные: без пробелов, с приведением регистра:
const schema = Yup.array()
.of(Yup.string())
.test('unique-normalized', 'Повторяющиеся значения', (value) => {
if (!value) return true;
const normalized = value.map(v => v.trim().toLowerCase());
return new Set(normalized).size === normalized.length;
});
Такой подход устраняет ложные различия между "Test",
" test " и "TEST".
В реальных схемах массивы часто содержат объекты. В этом случае уникальность определяется по конкретному полю:
const schema = Yup.array()
.of(
Yup.object({
id: Yup.number().required(),
name: Yup.string().required()
})
)
.test('unique-id', 'ID должны быть уникальными', (value) => {
if (!value) return true;
const ids = value.map(item => item.id);
return new Set(ids).size === ids.length;
});
Иногда один идентификатор недостаточен, и уникальность формируется комбинацией полей:
const schema = Yup.array()
.of(
Yup.object({
category: Yup.string().required(),
code: Yup.string().required()
})
)
.test('unique-composite', 'Дубликаты комбинаций недопустимы', (value) => {
if (!value) return true;
const signatures = value.map(
item => `${item.category}:${item.code}`
);
return new Set(signatures).size === signatures.length;
});
Такой способ используется для составных ключей без необходимости усложнять структуру данных.
При валидации массивов полезно фиксировать источник ошибки через
createError:
const schema = Yup.array().of(
Yup.string().test('unique-context', function (value) {
const { path, parent } = this;
const normalized = parent.map(v => v.toLowerCase());
const duplicates = normalized.filter((v, i, arr) => arr.indexOf(v) !== i);
if (duplicates.length > 0) {
return this.createError({
path,
message: 'Найдены дублирующиеся значения'
});
}
return true;
})
);
Здесь используется контекст выполнения теста для более точного контроля ошибки.
Для повторного использования логики создаётся расширение схемы:
Yup.addMethod(Yup.array, 'unique', function (message, mapper = v => v) {
return this.test('unique', message, function (list) {
if (!list) return true;
const mapped = list.map(mapper);
return new Set(mapped).size === mapped.length;
});
});
Использование:
const schema = Yup.array()
.of(Yup.string())
.unique('Значения должны быть уникальными');
Или для объектов:
const schema = Yup.array()
.of(Yup.object({ id: Yup.number() }))
.unique('Дубли ID', item => item.id);
При наличии зависимостей от внешних значений применяется контекст:
const schema = Yup.array().test(
'unique-conditional',
'Ошибка уникальности',
function (value) {
const { options } = this;
const strictMode = options.context?.strict;
if (!value) return true;
const normalized = strictMode
? value
: value.map(v => v.toLowerCase());
return new Set(normalized).size === normalized.length;
}
);
Такой подход позволяет менять правила проверки без изменения схемы.
transformПредобработка данных перед валидацией снижает сложность тестов:
const schema = Yup.array()
.transform((value) =>
value?.map(v => typeof v === 'string' ? v.trim() : v)
)
.test('unique', 'Дубликаты', (value) => {
if (!value) return true;
return new Set(value).size === value.length;
});
При сложных вложенных массивах проверка может выполняться на нескольких уровнях:
const schema = Yup.object({
groups: Yup.array().of(
Yup.object({
items: Yup.array()
.of(Yup.string())
.test('unique', 'Дубликаты внутри группы', (value) => {
if (!value) return true;
return new Set(value).size === value.length;
})
})
)
});
test.test, привязываются к пути
текущего узла схемы.abortEarly: false возможно накопление
нескольких ошибок уникальности в разных ветках структуры.Set остаётся
наиболее производительной по сравнению с filter и
indexOf.Использование indexOf:
value.filter((v, i, arr) => arr.indexOf(v) === i)
Использование Set:
new Set(value).size === value.length
Второй вариант обеспечивает линейную сложность и предпочтителен при больших объёмах данных.
const schema = Yup.array().test('unique-ignore-empty', function (value) {
if (!value) return true;
const filtered = value.filter(v => v !== null && v !== undefined && v !== '');
return new Set(filtered).size === filtered.length;
});
Такой подход часто используется при частично заполненных формах.