Специальные типы: null, undefined, void

В библиотеке валидации схем Zod тип null представлен отдельным примитивом и создаётся через z.null(). Его смысл строго ограничен: значение должно быть ровно null, без дополнительных вариантов.

import { z } from "zod";

const NullSchema = z.null();

NullSchema.parse(null); // проходит
NullSchema.parse(undefined); // ошибка
NullSchema.parse(0); // ошибка
NullSchema.parse(""); // ошибка

Ключевая особенность z.null() заключается в том, что он не допускает никаких альтернатив. В отличие от многих других типов, здесь отсутствует концепция приведения или преобразования. Проверка максимально строгая.

Часто null используется в схемах API-ответов, где сервер явно возвращает отсутствие значения, но не опускает поле полностью. Это принципиально отличается от undefined.

Пример комбинирования:

const NullableString = z.string().nullable();

NullableString.parse("text"); // ok
NullableString.parse(null); // ok
NullableString.parse(undefined); // ошибка

Метод .nullable() фактически преобразует тип в union: T | null, но при этом undefined остаётся недопустимым значением.


Отсутствие значения: undefined

Тип undefined в Zod создаётся через z.undefined(). Он описывает строгое соответствие значению undefined.

const UndefinedSchema = z.undefined();

UndefinedSchema.parse(undefined); // проходит
UndefinedSchema.parse(null); // ошибка
UndefinedSchema.parse("value"); // ошибка

Семантически undefined чаще всего связан с отсутствием значения в JavaScript-объектах, особенно при работе с необязательными полями.

Важная деталь: Zod различает undefined как значение и отсутствие ключа в объекте. Это влияет на поведение схем объектов:

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

Schema.parse({}); // ok
Schema.parse({ name: undefined }); // ok
Schema.parse({ name: "John" }); // ok

Метод .optional() фактически расширяет тип до T | undefined.


Отсутствие значения как часть union-типов

undefined и null часто используются в составе объединений, где требуется явно описать все возможные состояния данных.

const ValueSchema = z.union([
  z.string(),
  z.null(),
  z.undefined(),
]);

Такое объявление фиксирует три различных состояния:

  • строка как валидное значение
  • null как явное отсутствие значения
  • undefined как неинициализированное состояние

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


Тип void

void в Zod представлен через z.void(). Его поведение отличается от undefined, хотя в JavaScript эти значения часто путаются.

const VoidSchema = z.void();

VoidSchema.parse(undefined); // проходит
VoidSchema.parse(null); // ошибка
VoidSchema.parse("anything"); // ошибка

z.void() допускает только одно значение — undefined, но при этом концептуально обозначает «отсутствие возвращаемого значения», что ближе к сигнатурам функций, чем к данным.

Использование void наиболее характерно для описания возвращаемых значений функций:

const LoggerResult = z.void();

function logMessage(msg) {
  console.log(msg);
  return undefined;
}

LoggerResult.parse(logMessage("test"));

Сравнение null, undefined и void

Несмотря на близость в языке JavaScript, Zod рассматривает эти типы как строго разделённые сущности.

Тип Разрешённое значение Семантика
z.null() null явное отсутствие значения
z.undefined() undefined отсутствие инициализации
z.void() undefined отсутствие возвращаемого результата

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


Поведение в объектах и трансформациях

При использовании внутри z.object() различия становятся особенно заметны.

const Schema = z.object({
  a: z.null(),
  b: z.undefined(),
  c: z.void(),
});

Результаты валидации:

Schema.parse({
  a: null,
  b: undefined,
  c: undefined,
});

Любое отклонение от этих значений приводит к ошибке, включая отсутствие ключей, если поле не помечено как .optional().


Optional и Nullable как механизмы расширения

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

  • .optional() → добавляет undefined
  • .nullable() → добавляет null

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

const Schema = z.object({
  name: z.string().nullable().optional(),
});

Фактически это эквивалентно:

string | null | undefined

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


Особенности строгой валидации

Zod не выполняет неявных преобразований между null, undefined и void. Каждое значение проверяется строго на соответствие.

z.null().parse(undefined); // ошибка
z.undefined().parse(null); // ошибка
z.void().parse(null); // ошибка

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


Использование в типизации TypeScript

Одним из ключевых преимуществ Zod является генерация типов:

const schema = z.object({
  a: z.null(),
  b: z.undefined(),
  c: z.void(),
});

type Result = z.infer<typeof schema>;

Результат:

{
  a: null;
  b?: undefined;
  c: undefined;
}

Особенность заключается в том, что void в типах превращается в undefined, что соответствует поведению TypeScript, где void фактически является синонимом undefined в контексте возвращаемых значений.


Поведение при сериализации данных

При работе с JSON:

  • null сохраняется напрямую
  • undefined удаляется из JSON
  • void не сериализуется и фактически эквивалентен отсутствию возвращаемого значения
JSON.stringify({
  a: null,
  b: undefined,
});

Результат:

{"a":null}

Zod учитывает это поведение при проектировании схем, особенно в API-контрактах, где различие между отсутствующим полем и null имеет значение.


Применение в прикладных схемах

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

const UserSchema = z.object({
  name: z.string(),
  middleName: z.string().nullable(),
  nickname: z.string().optional(),
});

Здесь:

  • null обозначает отсутствие значения, заданное явно
  • undefined — отсутствие поля
  • void применяется редко и чаще в функциональных контекстах

Поведение в валидации массивов и вложенных структур

const Schema = z.array(z.null());

Schema.parse([null, null]); // ok
Schema.parse([null, undefined]); // ошибка

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