Валидация данных в JavaScript часто сталкивается с ситуациями, когда
часть структуры объекта может отсутствовать. Библиотека Superstruct
предоставляет встроенный механизм для описания таких случаев через
опциональные поля, позволяя точно контролировать допустимость
undefined и отсутствие ключей без усложнения схем.
Опциональность в Superstruct — это не просто «поле может быть пустым», а строго определённое поведение структуры, при котором значение либо присутствует и валидируется, либо полностью игнорируется как отсутствующее.
Функция optional() используется для обозначения
структуры, которая может отсутствовать или иметь значение
undefined, при этом не нарушая правила валидации.
import { object, string, optional } from 'superstruct'
const User = object({
name: string(),
nickname: optional(string())
})
В этом примере:
name — обязательное полеnickname — может отсутствовать полностью или быть
undefinednickname присутствует, оно обязано быть
строкойОпциональное поле проходит проверку в двух случаях:
undefinedvalidate({ name: 'Alex' }, User) // валидно
validate({ name: 'Alex', nickname: undefined }, User) // валидно
validate({ name: 'Alex', nickname: 'Al' }, User) // валидно
Однако значение null не считается эквивалентом
отсутствия:
validate({ name: 'Alex', nickname: null }, User) // ошибка
Это важное различие: optional() не делает поле
nullable.
Для поддержки null используется отдельный структурный
тип nullable().
import { nullable } from 'superstruct'
const User = object({
nickname: optional(nullable(string()))
})
Теперь допустимы следующие состояния:
undefinednullОпциональность применяется рекурсивно и может использоваться внутри вложенных объектов.
const Profile = object({
user: object({
name: string(),
contacts: optional(object({
email: string(),
phone: optional(string())
}))
})
})
Здесь:
contacts может отсутствовать полностьюcontacts поле phone также может
отсутствоватьОпциональность может применяться к структурам внутри массивов, но не делает сам массив «частично заполненным». Она работает только на уровне элементов структуры.
const Schema = object({
tags: optional(array(string()))
})
Возможные значения:
tagstags: undefinedtags: ['js', 'validation']Недопустимо:
tags: nulltags: [1, 2, 3]optional() не задаёт значение по умолчанию. Оно лишь
описывает допустимость отсутствия данных. Для автоматической подстановки
используется композиция с преобразованиями.
const withDefault = (struct, defaultValue) =>
coerce(optional(struct), (value) =>
value === undefined ? defaultValue : value
)
Пример использования:
const Settings = object({
theme: withDefault(string(), 'light')
})
Теперь:
theme отсутствует → используется
'light'Superstruct не имеет встроенного аналога partial в стиле
TypeScript, но optional() часто используется для
аналогичного поведения — частичного описания объекта.
const UpdateUser = object({
name: optional(string()),
email: optional(string()),
age: optional(number())
})
Такой объект допускает любое подмножество полей.
Однако важно учитывать: структура остаётся объектом с валидируемыми типами, а не «полностью свободной схемой».
Опциональные поля можно комбинировать с union() для
описания зависимых структур.
const Payment = object({
method: union([literal('card'), literal('cash')]),
cardNumber: optional(string())
})
Однако такая схема не запрещает логически некорректные комбинации.
Для этого требуется дополнительная валидация через
refine().
Опциональные поля часто используются совместно с пользовательскими проверками.
import { refine } from 'superstruct'
const Struct = object({
password: optional(refine(string(), 'minLength', (v) => v.length >= 8))
})
Поведение:
password допустимоПри валидации Superstruct не модифицирует входной объект. Это означает:
undefined не преобразуется в значенияconst [error, value] = validate({ name: 'Alex' }, User)
console.log(value.nickname) // undefined
Superstruct тесно интегрируется с TypeScript через вывод типов.
type User = Infer<typeof UserStruct>
Для опциональных полей результат будет:
{
name: string
nickname?: string
}
То есть optional() напрямую влияет на типизацию,
формируя необязательные свойства.
nickname: optional(string()) // null недопустим
optional(string()) // не задаёт значение
optional(object({ ... })) // объект либо есть, либо отсутствует целиком
const Config = object({
host: string(),
port: optional(number()),
debug: optional(boolean())
})
const Response = object({
data: object({
items: array(string()),
nextPage: optional(number())
})
})
const Patch = object({
title: optional(string()),
content: optional(string())
})
Опциональность хорошо комбинируется с базовыми примитивами Superstruct:
stringnumberbooleanarrayobjectunionПри этом optional() всегда работает как внешний слой над
структурой, не изменяя её внутреннюю логику.
optional(union([string(), number()]))
В Superstruct отсутствие поля трактуется строго:
undefined → считается отсутствующим только внутри
optional()null → отдельное значение, требующее
nullable()Такое разделение позволяет точно описывать контракт данных без неоднозначности, характерной для слабой типизации JavaScript.