Сортировка: ordered, items, sparse

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


items() — базовое описание схемы элементов массива

Метод items() задаёт схему (или набор схем) для элементов массива. Это основной инструмент, определяющий допустимые типы значений внутри коллекции.

Единая схема для всех элементов

Когда передаётся одна схема, она применяется ко всем элементам массива:

import Joi from 'joi';

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

schema.validate(['abc', 'def', 'gh']); 

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


Несколько типов элементов

Joi позволяет задавать несколько схем, формируя набор допустимых типов:

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

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


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

Если требуется строгая типизация, используется комбинация items() и дополнительных правил:

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

Здесь массив допускает только положительные целые числа и ограничен по длине.


ordered() — позиционная валидация элементов

Метод ordered() задаёт строгую последовательность схем для элементов массива. Каждый элемент соответствует конкретной позиции.

Строгая структура по индексам

const schema = Joi.array().ordered(
  Joi.string(),   // 0 индекс
  Joi.number(),   // 1 индекс
  Joi.boolean()   // 2 индекс
);

Пример валидного массива:

['user', 42, true]

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


Поведение при нарушении порядка

Если типы не совпадают с заданной последовательностью, валидация завершается ошибкой:

['user', true, 42] // ошибка: второй элемент должен быть number

Сочетание с ограничением длины

Часто ordered() используется вместе с .length():

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

Это усиливает гарантию фиксированной структуры данных.


sparse() — разрешение «разреженных» массивов

По умолчанию Joi требует, чтобы все элементы массива были явно определены. sparse() изменяет это поведение, разрешая undefined в качестве допустимого значения.

Разрешение пустых элементов

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

Теперь массив может содержать «пустоты»:

['a', undefined, 'c']

Без sparse() подобная структура вызовет ошибку валидации.


Поведение с undefined

Важно, что речь идёт именно о undefined, а не null:

['a', null, 'c'] // null не становится допустимым автоматически

Если требуется разрешить null, это задаётся явно:

Joi.array().items(Joi.string().allow(null)).sparse();

Применение в разреженных структурах

Разреженные массивы часто встречаются при работе с индексированными данными, где позиции важнее плотности заполнения:

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

Допускается:

['a', undefined, 'c']

Взаимодействие items, ordered и sparse

Эти методы могут использоваться совместно, но их поведение имеет приоритеты.

Комбинация items и ordered

const schema = Joi.array()
  .items(Joi.string())
  .ordered(Joi.number(), Joi.boolean());
  • ordered() задаёт строгие позиции для первых элементов
  • items() определяет правило для остальных элементов

Пример валидного массива:

[10, true, 'extra', 'values']

Добавление sparse в комбинированные схемы

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

Теперь допускаются пропуски:

[10, undefined, 'text']

Особенности поведения и приоритеты

Приоритет ordered() над items()

Если элемент попадает под правило ordered(), оно имеет приоритет над items():

Joi.array()
  .items(Joi.string())
  .ordered(Joi.number());

Первый элемент обязан быть числом, даже если items() допускает строки.


Ограничения структуры

  • ordered() фиксирует структуру по индексам
  • items() задаёт общий набор допустимых значений
  • sparse() управляет допустимостью undefined

Типичные сценарии применения

Строгие DTO-структуры

Joi.array().ordered(
  Joi.string(),
  Joi.number(),
  Joi.boolean()
);

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


Гибридные массивы

Joi.array().items(
  Joi.string(),
  Joi.number()
);

Подходит для неструктурированных данных.


Индексированные структуры с пропусками

Joi.array()
  .ordered(
    Joi.string(),
    Joi.string(),
    Joi.string()
  )
  .sparse();

Используется при работе с данными, где позиции важнее полноты заполнения.


Поведение ошибок валидации

  • Нарушение типа в items() приводит к ошибке элемента
  • Нарушение позиции в ordered() приводит к структурной ошибке массива
  • undefined без sparse() считается недопустимым значением
  • null не становится допустимым автоматически

Модель интерпретации массива в Joi

С точки зрения Joi массив можно представить как систему трёх уровней:

  • Структурный уровень (ordered) — фиксирует позиции
  • Типовой уровень (items) — определяет допустимые значения
  • Семантический уровень (sparse) — регулирует полноту данных

Такое разделение позволяет точно описывать даже сложные формы данных, сохраняя предсказуемость валидации и контроль над структурой массива.