Валидация элементов массива

В библиотеке Joi работа с массивами строится вокруг схемы Joi.array(), которая позволяет задавать строгие правила как для самого массива, так и для каждого его элемента. Основная идея заключается в том, что массив рассматривается как контейнер, внутри которого каждый элемент проходит собственную валидацию по заранее заданным правилам.

При создании схемы массива используется метод items(), в который передаётся схема элементов:

const Joi = require('joi');

const schema = Joi.array().items(Joi.string());

В этом случае массив считается валидным только тогда, когда каждый его элемент является строкой. Любое несоответствие типу приводит к ошибке валидации.

Пример проверяемых данных:

schema.validate(['a', 'b', 'c']); // валидно
schema.validate(['a', 1, 'c']);   // ошибка

Схема элементов может быть любой сложности, включая вложенные объекты:

const schema = Joi.array().items(
  Joi.object({
    id: Joi.number().required(),
    name: Joi.string().required()
  })
);

Каждый элемент массива в этом случае должен быть объектом с обязательными полями id и name.

Ограничение количества элементов

Для контроля размера массива применяются методы min() и max():

const schema = Joi.array().min(2).max(5);

Такой массив должен содержать от двух до пяти элементов включительно. Нарушение границ вызывает ошибку валидации.

Также используется length(), когда требуется строго фиксированное количество элементов:

const schema = Joi.array().length(3);

Проверка уникальности элементов

Метод unique() гарантирует отсутствие повторяющихся значений:

const schema = Joi.array().items(Joi.number()).unique();

Массив [1, 2, 3] будет валиден, а [1, 2, 2] — нет.

Для более сложных структур можно задавать правило уникальности по ключу:

const schema = Joi.array().items(
  Joi.object({
    id: Joi.number(),
    email: Joi.string()
  })
).unique('id');

В этом случае проверяется уникальность значений поля id внутри объектов.

Частичная и условная валидация элементов

Метод has() задаёт правило, что хотя бы один элемент массива должен соответствовать определённой схеме:

const schema = Joi.array().items(Joi.number()).has(Joi.number().greater(10));

Такой массив обязан содержать минимум одно число больше 10.

Дополнительная гибкость достигается через alternatives() внутри items(), позволяя комбинировать несколько типов:

const schema = Joi.array().items(
  Joi.alternatives().try(
    Joi.string(),
    Joi.number()
  )
);

Здесь каждый элемент может быть либо строкой, либо числом.

Строгий порядок элементов

Метод ordered() используется, когда важно не только содержимое, но и позиция элементов:

const schema = Joi.array().ordered(
  Joi.string(),
  Joi.number(),
  Joi.boolean()
);

Такой массив должен строго соответствовать порядку: сначала строка, затем число, затем булево значение.

Пример:

schema.validate(['text', 10, true]); // валидно
schema.validate([10, 'text', true]); // ошибка

Допустимость неизвестных элементов

По умолчанию Joi не запрещает дополнительные элементы, если они соответствуют общей схеме items(). Однако поведение можно контролировать через sparse() и строгие схемы объектов внутри массива.

При использовании объектов часто применяется строгая схема:

const schema = Joi.array().items(
  Joi.object({
    id: Joi.number().required()
  }).strict()
);

Это усиливает контроль над структурой каждого элемента.

Разрешение отдельных типов значений

Иногда требуется допустить одиночное значение вместо массива. Для этого используется single():

const schema = Joi.array().items(Joi.string()).single();

В этом случае допустимы как массив ['a', 'b'], так и одиночное значение 'a', которое автоматически рассматривается как массив из одного элемента.

Вложенные массивы и рекурсивная валидация

Joi поддерживает многомерные структуры:

const schema = Joi.array().items(
  Joi.array().items(Joi.number())
);

Такой вариант описывает массив массивов чисел.

Валидация выполняется рекурсивно: каждый уровень структуры проверяется отдельно по своей схеме.

Кастомная валидация элементов массива

Для сложных случаев используется custom(), позволяющий определить собственную логику проверки:

const schema = Joi.array().items(
  Joi.number().custom((value, helpers) => {
    if (value % 2 !== 0) {
      return helpers.error('number.odd');
    }
    return value;
  })
);

Здесь каждый элемент массива проверяется на чётность, и при нарушении возвращается ошибка.

Комбинирование ограничений

На практике схемы массивов часто содержат сразу несколько ограничений:

const schema = Joi.array()
  .items(Joi.number().integer())
  .min(1)
  .max(10)
  .unique()
  .has(Joi.number().positive());

Такое описание требует, чтобы массив:

  • содержал только целые числа
  • имел размер от 1 до 10 элементов
  • не содержал дубликатов
  • включал хотя бы одно положительное число

Подобная композиция позволяет создавать строгие и выразительные правила для структурированных данных.

Валидация сложных структур в реальных сценариях

При работе с API часто встречается массив объектов:

const schema = Joi.array().items(
  Joi.object({
    userId: Joi.number().required(),
    roles: Joi.array().items(Joi.string()).min(1)
  })
);

В этом случае каждый элемент массива представляет пользователя, а внутри него дополнительно валидируется вложенный массив ролей. Joi обрабатывает такую структуру по уровням, обеспечивая независимую проверку каждого слоя данных.