Null и undefined

В Superstruct значения null и undefined рассматриваются как принципиально разные состояния отсутствия данных, и это различие напрямую влияет на проектирование схем валидации и поведение структур при преобразовании входных значений.

В JavaScript undefined обычно означает отсутствие значения как такового: переменная не инициализирована, поле объекта не задано, аргумент функции не передан. null же является явным сигналом «пустого значения», заданного намеренно. В контексте Superstruct эти различия не игнорируются, поскольку библиотека стремится к строгой и предсказуемой валидации входных данных.

При работе со схемами Superstruct важно учитывать, что базовые структуры по умолчанию не принимают ни null, ни undefined, если это явно не указано.

import { string, assert } from 'superstruct'

assert('text', string)      // проходит
assert(undefined, string)   // ошибка
assert(null, string)        // ошибка

Такое поведение делает валидацию строгой: структура ожидает конкретный тип и не допускает неопределённости без явного разрешения.

Поведение undefined и optional

Для обработки отсутствующих полей используется обёртка optional. Она разрешает undefined, но не допускает null.

import { string, optional, assert } from 'superstruct'

const Name = optional(string)

assert(undefined, Name) // проходит
assert('Alice', Name)    // проходит
assert(null, Name)       // ошибка

Ключевая особенность optional заключается в том, что отсутствие значения считается допустимым состоянием, но намеренно переданный null трактуется как некорректное значение.

Это важно при моделировании API-ответов или форм, где поле может быть не задано, но не должно содержать явный null.

Поведение null и nullable

Для случаев, когда null является допустимым значением, используется nullable.

import { string, nullable, assert } from 'superstruct'

const Nickname = nullable(string)

assert('John', Nickname)  // проходит
assert(null, Nickname)    // проходит
assert(undefined, Nickname) // ошибка

Здесь null трактуется как валидное состояние, например «значение отсутствует по смыслу», тогда как undefined остаётся сигналом отсутствия поля.

Комбинирование nullable и optional

На практике часто требуется поддержка обоих состояний: и null, и undefined. Для этого структуры комбинируются через optional(nullable(...)).

import { string, optional, nullable, assert } from 'superstruct'

const Bio = optional(nullable(string))

assert('developer', Bio)  // проходит
assert(null, Bio)         // проходит
assert(undefined, Bio)    // проходит

Такая схема отражает максимально гибкую модель данных, где поле может быть:

  • отсутствующим (undefined)
  • явно пустым (null)
  • заполненным значением

Union как способ явного описания null и undefined

Более явный способ описания допускаемых значений — использование union, где null и undefined становятся полноценными вариантами типа.

import { string, union, assert } from 'superstruct'

const Value = union([string, null, undefined])

assert('ok', Value)
assert(null, Value)
assert(undefined, Value)

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

Разница между optional и union с undefined

Хотя optional(struct) и union([struct, undefined]) на первый взгляд эквивалентны, между ними есть поведенческое различие на уровне семантики:

  • optional — выражает отсутствие поля как допустимое состояние
  • union — выражает undefined как полноценное значение среди других вариантов

Это различие становится критичным при трансформациях и сериализации схем, где важно различать «поле отсутствует» и «поле задано как undefined».

Взаимодействие с create и преобразованием данных

Функция create в Superstruct приводит входные данные к валидному виду, но не изменяет семантику null и undefined без дополнительных преобразователей.

import { string, optional, create } from 'superstruct'

const Name = optional(string)

create(undefined, Name) // undefined
create('Alice', Name)   // 'Alice'

При этом null не преобразуется в undefined автоматически и будет считаться ошибкой, если не предусмотрена соответствующая схема.

Типовые ошибки при работе с null и undefined

Наиболее частая ошибка заключается в ожидании, что optional автоматически примет null. Это приводит к несоответствию данных API и схемы валидации.

const Age = optional(number)

Age.parse(null) // ошибка

Корректная модель требует явного указания допустимости null, иначе структура остаётся строгой.

Другая ошибка связана с избыточным использованием union, где вместо семантически ясного optional применяется расширенный набор типов, что ухудшает читаемость схем.

Проектирование схем с учётом null и undefined

При построении структур данных обычно выделяются три модели:

  • строгая модель: только конкретный тип
  • опциональная модель: допускается отсутствие значения
  • nullable модель: допускается явное пустое значение

Комбинация этих моделей позволяет точно описывать поведение данных на границе системы.

import { number, optional, nullable } from 'superstruct'

const Score = number
const OptionalScore = optional(number)
const NullableScore = nullable(number)
const FlexibleScore = optional(nullable(number))

Такая декомпозиция делает схемы предсказуемыми и упрощает контроль входных данных в прикладных системах.