В Superstruct значения null и undefined
рассматриваются как принципиально разные состояния отсутствия данных, и
это различие напрямую влияет на проектирование схем валидации и
поведение структур при преобразовании входных значений.
В JavaScript undefined обычно означает отсутствие
значения как такового: переменная не инициализирована, поле объекта не
задано, аргумент функции не передан. null же является явным
сигналом «пустого значения», заданного намеренно. В контексте
Superstruct эти различия не игнорируются, поскольку библиотека стремится
к строгой и предсказуемой валидации входных данных.
При работе со схемами Superstruct важно учитывать, что базовые
структуры по умолчанию не принимают ни null, ни
undefined, если это явно не указано.
import { string, assert } from 'superstruct'
assert('text', string) // проходит
assert(undefined, string) // ошибка
assert(null, string) // ошибка
Такое поведение делает валидацию строгой: структура ожидает конкретный тип и не допускает неопределённости без явного разрешения.
Для обработки отсутствующих полей используется обёртка
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.
import { string, nullable, assert } from 'superstruct'
const Nickname = nullable(string)
assert('John', Nickname) // проходит
assert(null, Nickname) // проходит
assert(undefined, Nickname) // ошибка
Здесь null трактуется как валидное состояние, например
«значение отсутствует по смыслу», тогда как undefined
остаётся сигналом отсутствия поля.
На практике часто требуется поддержка обоих состояний: и
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
становятся полноценными вариантами типа.
import { string, union, assert } from 'superstruct'
const Value = union([string, null, undefined])
assert('ok', Value)
assert(null, Value)
assert(undefined, Value)
Такой подход используется, когда важно явно задокументировать все допустимые состояния данных, особенно в сложных схемах, где поведение должно быть максимально прозрачным.
Хотя optional(struct) и
union([struct, undefined]) на первый взгляд эквивалентны,
между ними есть поведенческое различие на уровне семантики:
optional — выражает отсутствие поля как допустимое
состояниеunion — выражает undefined как полноценное
значение среди других вариантовЭто различие становится критичным при трансформациях и сериализации схем, где важно различать «поле отсутствует» и «поле задано как undefined».
Функция create в Superstruct приводит входные данные к
валидному виду, но не изменяет семантику null и
undefined без дополнительных преобразователей.
import { string, optional, create } from 'superstruct'
const Name = optional(string)
create(undefined, Name) // undefined
create('Alice', Name) // 'Alice'
При этом null не преобразуется в undefined
автоматически и будет считаться ошибкой, если не предусмотрена
соответствующая схема.
Наиболее частая ошибка заключается в ожидании, что
optional автоматически примет null. Это
приводит к несоответствию данных API и схемы валидации.
const Age = optional(number)
Age.parse(null) // ошибка
Корректная модель требует явного указания допустимости
null, иначе структура остаётся строгой.
Другая ошибка связана с избыточным использованием union,
где вместо семантически ясного optional применяется
расширенный набор типов, что ухудшает читаемость схем.
При построении структур данных обычно выделяются три модели:
Комбинация этих моделей позволяет точно описывать поведение данных на границе системы.
import { number, optional, nullable } from 'superstruct'
const Score = number
const OptionalScore = optional(number)
const NullableScore = nullable(number)
const FlexibleScore = optional(nullable(number))
Такая декомпозиция делает схемы предсказуемыми и упрощает контроль входных данных в прикладных системах.