Работа с unknown данными

Природа неизвестных данных в JavaScript-окружении

Большая часть входных данных в JavaScript-приложениях поступает из внешних источников: HTTP-запросы, формы, WebSocket-сообщения, localStorage, сторонние API. На этапе выполнения тип этих данных заранее не определён. Даже если в коде ожидается объект определённой формы, фактически приходит значение типа unknown — строка, число, null, произвольный объект или структура с неожиданными полями.

TypeScript не решает эту проблему автоматически, поскольку проверка типов существует только на этапе компиляции. В рантайме любое значение требует валидации. Именно в этом контексте Zod выступает как инструмент строгого описания и проверки структуры данных.

Тип unknown как базовая точка входа

В TypeScript тип unknown используется как безопасная альтернатива any. Любое значение можно привести к unknown, но дальнейшая работа с ним невозможна без явной проверки.

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

import { z } from "zod";

const schema = z.string();

const value: unknown = JSON.parse('"hello"');

const result = schema.parse(value);

Здесь value рассматривается как полностью неопределённый, и только parse устанавливает его соответствие ожидаемому типу.

Базовые стратегии обработки неизвестных данных

Работа с неизвестными данными в Zod обычно строится вокруг трёх механизмов:

  • строгая проверка через parse
  • безопасная проверка через safeParse
  • предварительная нормализация через preprocess
Строгая проверка через parse

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

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

schema.parse(JSON.parse('{"id": 1}'));

При несоответствии структуры выбрасывается ZodError, содержащий подробную информацию о проблеме.

Безопасная проверка через safeParse

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

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

const result = schema.safeParse(JSON.parse('{"id": "wrong"}'));

if (!result.success) {
  result.error.issues;
} else {
  result.data;
}

safeParse возвращает объект с двумя возможными состояниями: успех или ошибка, что делает его удобным для обработки неопределённых входов без прерывания потока выполнения.

z.unknown и z.any как отражение неопределённости

Zod предоставляет два специальных типа для работы с полностью неизвестными данными:

  • z.unknown() — строгий вариант неизвестного значения
  • z.any() — полностью отключает проверку
const unknownSchema = z.unknown();
const anySchema = z.any();

Различие между ними принципиальное. unknown требует дальнейшей проверки, тогда как any фактически исключает валидацию.

В контексте работы с внешними данными z.unknown() используется как промежуточный этап перед уточнением структуры.

Постепенное сужение типа через композицию схем

Одна из ключевых моделей работы с неизвестными данными — постепенное уточнение структуры.

const base = z.unknown();

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

const parsed = objectSchema.parse(base.parse(JSON.parse('{"name":"A"}')));

На практике промежуточный unknown этап часто опускается, но концептуально он отражает поток данных: от неопределённого состояния к строго типизированному.

Обработка JSON как типичный источник unknown

JSON является основным источником данных неопределённого типа. Любой результат JSON.parse имеет тип any, но по сути представляет собой unknown.

const raw = JSON.parse(input);

const schema = z.object({
  userId: z.number(),
  active: z.boolean(),
});

const validated = schema.safeParse(raw);

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

Нормализация входных данных через preprocess

Когда данные приходят в нестабильном формате, например строка вместо числа, применяется z.preprocess.

const schema = z.object({
  id: z.preprocess((val) => Number(val), z.number()),
});

Функция preprocess получает unknown и преобразует его перед валидацией. Это особенно важно при работе с query-параметрами URL или формами HTML.

Типичный случай:

  • "123"123
  • undefinedNaN (с последующей ошибкой валидации)

Явное преобразование типов через z.coerce

Zod предоставляет более высокоуровневый механизм — z.coerce, который автоматически приводит значения к нужному типу.

const schema = z.object({
  id: z.coerce.number(),
});

В этом случае строки, содержащие числа, автоматически приводятся к number, если это возможно.

coerce уменьшает необходимость ручной обработки unknown, но сохраняет контроль над финальной валидацией.

Работа с частично неизвестными объектами

В реальных API часто встречаются структуры, где часть полей известна, а часть — нет.

const schema = z.object({
  id: z.number(),
}).passthrough();

Метод passthrough позволяет сохранять неизвестные поля без валидации. Альтернативой является strict, который, наоборот, запрещает любые лишние ключи.

const strictSchema = z.object({
  id: z.number(),
}).strict();

Таким образом, поведение при встрече с unknown-ключами контролируется явно.

Работа с динамическими ключами через z.record

Когда структура объекта неизвестна полностью, но известен тип значений, используется z.record.

const schema = z.record(z.number());

Это означает, что ключи могут быть любыми строками, а значения обязаны быть числами.

Такой подход часто применяется для:

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

Union-тип как способ обработки вариативного unknown

Неизвестные данные часто имеют несколько возможных форм. Для этого используется z.union.

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

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

Discriminated union как оптимизация проверки unknown

Если в данных присутствует поле-дискриминатор, используется discriminatedUnion, позволяющий эффективно определять структуру.

const schema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("user"), name: z.string() }),
  z.object({ type: z.literal("admin"), level: z.number() }),
]);

Это снижает количество проверок и делает разбор неизвестных данных детерминированным.

Ошибки валидации и анализ структуры unknown

При несоответствии данных схеме Zod возвращает ZodError.

const result = z.string().safeParse(123);

Объект ошибки содержит:

  • путь к проблемному полю
  • ожидаемый тип
  • полученное значение

Дополнительные методы:

error.flatten();
error.format();

flatten группирует ошибки по полям, упрощая обработку вложенных структур.

Глубоко вложенные неизвестные структуры

При работе с API часто встречаются вложенные объекты:

const schema = z.object({
  user: z.object({
    profile: z.object({
      age: z.number(),
    }),
  }),
});

Даже если верхний уровень частично известен, внутренние поля остаются потенциально unknown до завершения полной проверки.

Zod последовательно проходит по структуре, превращая каждый уровень из неизвестного состояния в строго типизированный.

Защита от полностью произвольных объектов

Иногда входные данные представляют собой полностью неопределённые структуры.

const schema = z.object({
  data: z.unknown(),
});

Здесь unknown выступает как явный маркер отсутствия информации о содержимом. Дальнейшая работа с data требует дополнительной валидации через вложенные схемы.

Комбинирование unknown с refine

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

const schema = z.string().refine((val) => val.length > 3);

При работе с неизвестными данными refine часто используется как финальный слой проверки после приведения типа.

Поток обработки неизвестных данных

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

  • unknown вход
  • предварительная нормализация (preprocess, coerce)
  • структурная проверка (object, union, record)
  • уточнение через refine
  • получение строго типизированного результата

Каждый этап уменьшает степень неопределённости и приближает данные к предсказуемой структуре.

Роль Zod в управлении неопределённостью рантайма

JavaScript не предоставляет встроенного механизма описания контрактов данных. Любое значение потенциально является unknown, пока не проверено. Zod формализует этот процесс через декларативные схемы, превращая неопределённые входы в строго структурированные объекты без потери информации о возможных ошибках и отклонениях структуры.