Оптимизация сложных схем

Оптимизация схем в Zod становится критичной при росте вложенности данных, увеличении количества валидаций и активном использовании трансформаций. Основная проблема сложных схем заключается не только в размере, но и в количестве операций, выполняемых при каждом вызове parse или safeParse.


Структурирование схем и контроль глубины вложенности

Глубоко вложенные объекты увеличивают стоимость обхода дерева валидации. Каждое поле добавляет дополнительные проверки типов, а при наличии трансформаций цепочка обработки удлиняется.

Типичная ошибка — избыточное вложение:

const schema = z.object({
  user: z.object({
    profile: z.object({
      settings: z.object({
        theme: z.string(),
      }),
    }),
  }),
});

Каждый уровень увеличивает количество операций обхода. Более оптимальный подход — плоская структура там, где это допустимо:

const schema = z.object({
  userProfileSettingsTheme: z.string(),
});

Снижение глубины напрямую уменьшает стоимость рекурсивного обхода.


Использование discriminatedUnion вместо union

Обычный z.union выполняет последовательную проверку всех вариантов, что в сложных схемах приводит к линейному росту стоимости.

const schema = z.union([
  z.object({ type: z.literal("a"), value: z.string() }),
  z.object({ type: z.literal("b"), count: z.number() }),
]);

При каждом парсинге выполняется попытка сопоставления каждого варианта.

Оптимизированный вариант — discriminatedUnion, который использует дискриминатор для мгновенного выбора ветки:

const schema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("a"), value: z.string() }),
  z.object({ type: z.literal("b"), count: z.number() }),
]);

Сложность уменьшается с O(n) до O(1) на этапе выбора ветки.


Минимизация использования refine и superRefine

Функции refine и superRefine выполняются после базовой валидации и могут содержать произвольную логику. Их чрезмерное использование приводит к значительным затратам производительности, особенно в массивах.

const schema = z.object({
  age: z.number().refine((v) => v >= 0 && v <= 120),
});

Вместо этого предпочтительнее использовать встроенные ограничения:

const schema = z.object({
  age: z.number().min(0).max(120),
});

Встроенные валидаторы оптимизированы и не требуют пользовательской функции.


Избежание создания схем внутри функций

Частая ошибка — генерация схем при каждом вызове функции. Это приводит к повторному созданию объектов схем, что увеличивает нагрузку на сборщик мусора и снижает кеширование внутренних структур Zod.

function createSchema() {
  return z.object({
    id: z.string(),
    name: z.string(),
  });
}

Оптимальный подход — вынос схемы в область модуля:

const schema = z.object({
  id: z.string(),
  name: z.string(),
});

Схема становится переиспользуемой, а внутренние структуры Zod могут кешироваться.


Применение lazy для рекурсивных структур

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

const schema = z.object({
  name: z.string(),
  children: z.array(schema),
});

Корректная оптимизация:

const schema = z.lazy(() =>
  z.object({
    name: z.string(),
    children: z.array(schema),
  })
);

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


Разделение больших схем на композиции

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

const baseUser = z.object({
  id: z.string(),
  email: z.string(),
});

const userWithProfile = baseUser.extend({
  profile: z.object({
    bio: z.string(),
  }),
});

Композиция через extend, merge, pick, omit снижает дублирование и позволяет Zod повторно использовать уже проверенные поддеревья.

Особенно эффективны операции:

  • pick для выделения только нужных полей
  • omit для исключения ненужных веток
  • partial для частичной валидации

Снижение стоимости трансформаций

Метод transform добавляет дополнительный проход по данным. При массовой обработке это становится узким местом.

const schema = z.string().transform((v) => v.trim().toLowerCase());

При больших объёмах данных трансформации выгоднее выносить за пределы схемы, если они не являются частью валидации.


Контроль строгих режимов объекта

Режимы strict, passthrough и strip влияют на количество операций обработки ключей.

  • strict добавляет проверку каждого лишнего поля
  • passthrough уменьшает количество проверок, но сохраняет данные
  • strip удаляет лишние ключи без дополнительной логики
const schema = z.object({
  id: z.string(),
}).strip();

Для высоконагруженных сценариев strip снижает стоимость обработки входного объекта.


Оптимизация массивов и больших коллекций

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

const schema = z.array(
  z.object({
    id: z.string(),
    value: z.number(),
  })
);

Оптимизации:

  • минимизация вложенности объектов в элементах массива
  • использование примитивных схем внутри коллекций
  • избегание refine внутри элементов массива
  • использование z.tuple вместо массива при фиксированной структуре

Использование safeParse вместо исключений

Метод parse генерирует исключения при ошибке, что увеличивает стоимость обработки в сценариях с массовой валидацией.

const result = schema.safeParse(data);

safeParse снижает накладные расходы на обработку ошибок в высоконагруженных системах.


Повторное использование под-схем

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

const addressSchema = z.object({
  city: z.string(),
  street: z.string(),
});

const userSchema = z.object({
  name: z.string(),
  address: addressSchema,
});

const orderSchema = z.object({
  deliveryAddress: addressSchema,
});

Такой подход уменьшает общий размер графа схем и улучшает кеширование.


Ленивая композиция и условная структура

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

const schema = z.lazy(() =>
  condition
    ? schemaA
    : schemaB
);

Это уменьшает количество активных узлов схемы в памяти.


Оптимизация через ограничение валидации на ранних этапах

Чем раньше происходит отсечение некорректных данных, тем меньше затрат на дальнейшие проверки.

const schema = z.object({
  type: z.literal("event"),
  payload: z.object({
    id: z.string(),
  }),
});

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


Снижение стоимости optional и nullable

Чрезмерное использование optional и nullable усложняет ветвление логики валидации.

const schema = z.object({
  value: z.string().optional(),
});

Оптимизация достигается за счёт явного разделения сценариев данных через union:

const schema = z.union([
  z.object({ value: z.string() }),
  z.object({}),
]);