Отладка сложных схем

При работе со сложными схемами в Zod основная трудность возникает не на этапе описания типов, а при интерпретации причин провала валидации. Чем глубже вложенность объектов, массивов, объединений и трансформаций, тем менее очевидным становится источник ошибки. В таких условиях ключевым становится системный подход к анализу ZodError, структуры issues и поведения композиционных схем.


Базовая модель ошибок Zod

Любая ошибка валидации в Zod сводится к объекту ZodError, содержащему массив issues. Каждый элемент описывает конкретное нарушение:

  • path — путь до значения в структуре данных
  • message — текстовое описание проблемы
  • code — тип ошибки (например, invalid_type, too_small, unrecognized_keys)
  • дополнительные поля зависят от типа схемы

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


Интерпретация path в глубоко вложенных структурах

Вложенные объекты формируют массив путей, который может включать индексы массивов и ключи объектов одновременно:

path: ["user", "addresses", 2, "zipCode"]

Такой путь означает:

  • user — корневой объект
  • addresses — массив
  • 2 — третий элемент массива
  • zipCode — конкретное поле

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


safeParse как основной инструмент изоляции ошибок

Использование safeParse вместо parse позволяет избежать исключений и получить структурированный результат:

const result = schema.safeParse(data);

if (!result.success) {
  console.log(result.error.issues);
}

В сложных схемах это позволяет:

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

Проблема каскадных ошибок

В глубоко вложенных схемах одна ошибка часто порождает десятки вторичных. Например, если объект не соответствует типу, все вложенные поля могут быть помечены как некорректные.

Типичный пример:

  • поле user не объект
  • внутри ожидаются user.name, user.profile.age
  • все вложенные пути также падают

Такие ситуации требуют фильтрации первопричин. Практически полезно выделять ошибки с минимальным path как первичные.


Разделение ошибок по типам code

Поле code позволяет классифицировать ошибки:

  • invalid_type — несоответствие типа
  • too_small / too_big — нарушения ограничений
  • invalid_union — не совпал ни один вариант
  • unrecognized_keys — лишние поля
  • custom — ошибки из refine или superRefine

При отладке сложных схем важно группировать ошибки по code, поскольку это часто быстрее приводит к источнику проблемы, чем анализ сообщений.


Отладка union-схем

z.union([...]) и особенно z.discriminatedUnion часто становятся источником непрозрачных ошибок.

В случае обычного union Zod пытается проверить каждую схему и возвращает агрегированный результат. Это приводит к ситуации, когда:

  • каждая альтернатива даёт собственные ошибки
  • итоговый invalid_union скрывает реальную причину

Структура unionErrors внутри ZodError позволяет разложить каждую попытку отдельно:

  • первая схема: набор ошибок
  • вторая схема: набор ошибок
  • итог: отсутствие совпадения

При анализе важно не рассматривать итоговую ошибку как единичную, а раскладывать её по альтернативам.


Discriminated union и несоответствие дискриминатора

При использовании discriminatedUnion основной источник проблем — некорректное значение дискриминирующего поля.

Типичная ситуация:

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

Если type отсутствует или имеет другое значение, Zod сразу отбрасывает все варианты.

Отладка сводится к проверке:

  • наличия дискриминатора
  • точного соответствия literal
  • отсутствия трансформаций до валидации

superRefine как источник скрытых ошибок

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

schema.superRefine((val, ctx) => {
  if (val.a < val.b) {
    ctx.addIssue({
      code: "custom",
      message: "a must be >= b",
      path: ["a"]
    });
  }
});

Основные сложности:

  • ошибки не имеют стандартного code
  • путь задаётся вручную
  • возможны логические конфликты с базовой схемой

При отладке важно разделять:

  • структурные ошибки Zod
  • логические ошибки бизнес-уровня

Проблемы transform и потеря контекста

transform может маскировать исходное значение, что усложняет диагностику:

z.string().transform((val) => Number(val))

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

Для отладки используют:

  • временное отключение transform
  • промежуточные схемы
  • логирование до и после преобразования

Глубокая типизация и расхождения runtime vs compile-time

TypeScript-типизация Zod через z.infer может создавать иллюзию корректности схемы, даже если runtime-валидация ломается.

Типичные расхождения:

  • optional поля становятся required в runtime
  • union типов не покрывает реальные данные
  • transform меняет тип, но не отражается в inferred типе

При анализе сложных схем важно проверять именно runtime-структуру, а не только типы.


Работа с массивами и индексированными ошибками

Ошибки в массивах часто выглядят перегруженными:

path: ["items", 5, "price"]

При массовых ошибках полезно:

  • группировать по индексу
  • определять «битые» элементы массива
  • проверять схему элемента отдельно от массива

Часто оказывается, что ошибка не в массиве как структуре, а в единичной модели элемента.


Агрегация и нормализация ошибок

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

  • группировка по path
  • группировка по code
  • выделение первичных ошибок
  • подавление каскадных дубликатов

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


Логирование промежуточных схем

В сложных композициях полезно разбивать схемы на этапы:

  • базовая структура
  • валидация вложенных объектов
  • union-ветвления
  • финальные трансформации

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


Частые источники ложных ошибок

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

  • undefined вместо отсутствующего поля
  • пустые строки вместо null
  • неожиданные NaN в числах
  • лишние ключи в объектах
  • частично заполненные union-объекты

В таких случаях ошибка Zod корректна, но данные не соответствуют контракту.


Итоговая модель анализа сложных схем

Диагностика в Zod сводится к последовательному разложению:

  • структура issues
  • пути path
  • типы code
  • разветвления union
  • влияние transform и refine

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