Преобразование входных данных в структурированную форму является одной из ключевых задач при работе с внешними источниками информации: API, формами, файлами конфигурации. В библиотеке Superstruct эта задача решается через механизмы принудительного приведения типов и трансформации значений, которые позволяют не только проверять данные, но и нормализовать их в нужный формат ещё до попадания в бизнес-логику.
В стандартной модели валидации Superstruct входное значение либо
соответствует структуре, либо нет. Однако в реальных сценариях данные
часто приходят в «грязном» виде: числа в виде строк, даты в текстовом
формате, булевы значения как "true" и
"false".
Механизм coercion (приведения типов) решает проблему на уровне схемы, позволяя автоматически преобразовывать входные значения перед проверкой.
Ключевая идея:
coerce выполняется до валидации, transform может выполняться как до, так и после, в зависимости от композиции структуры.
Функция coerce позволяет задать правило преобразования
значения, если оно соответствует определённому типу или условию.
Общая форма:
coerce(structure, (value) => transformedValue)
Пример приведения строки к числу:
import { coerce, number, string, validate } from 'superstruct'
const NumberFromString = coerce(number(), string(), (value) => {
return Number(value)
})
validate(NumberFromString, "42")
// => [null, 42]
Здесь происходит следующая цепочка:
"42" соответствует строке42numberЕсли бы преобразование не произошло, значение "42" было
бы отклонено.
Coerce часто используется для обработки «широких» входных типов.
Пример: обработка числа, которое может прийти как строка или как число:
import { coerce, number, string, union } from 'superstruct'
const LooseNumber = coerce(number(), union([number(), string()]), (value) => {
return typeof value === 'string' ? Number(value) : value
})
Такой подход позволяет работать с API, которые не гарантируют строгую типизацию.
Частый кейс — преобразование "true" и
"false" в boolean:
import { coerce, boolean, string } from 'superstruct'
const BooleanFromString = coerce(boolean(), string(), (value) => {
if (value === 'true') return true
if (value === 'false') return false
return value
})
Здесь важно учитывать, что возвращаемое значение может остаться без изменений, если оно уже не строка, что позволяет использовать структуру гибко.
В отличие от coerce, функция transform
применяется к уже валидированному значению. Это позволяет изменять
данные после проверки структуры.
Базовая форма:
transform(structure, (value) => newValue)
Пример преобразования строки в Date:
import { string, transform } from 'superstruct'
const DateFromString = transform(string(), (value) => {
return new Date(value)
})
Такой подход гарантирует, что на вход поступает корректная строка, а
на выходе всегда объект Date.
Разделение этих механизмов принципиально:
Это различие влияет на безопасность и предсказуемость поведения.
Пример:
const A = coerce(number(), string(), Number)
Если передать "abc", результат будет NaN,
но структура уже считает значение числом, и дальнейшая валидация может
пройти некорректно.
С transform ситуация иная:
const B = transform(string(), (v) => new Date(v))
Здесь гарантируется, что вход — строка, а результат всегда Date, но отсутствие проверки корректности даты должно контролироваться отдельно.
Обе операции можно комбинировать для построения многоступенчатой обработки данных.
Пример: строка → число → округление:
import { coerce, number, string, transform } from 'superstruct'
const RoundedNumber = transform(
coerce(number(), string(), Number),
(value) => Math.round(value)
)
Цепочка выглядит так:
Coerce и transform применяются не только к примитивам, но и к объектам.
Пример нормализации объекта конфигурации:
import { object, string, number, coerce } from 'superstruct'
const Config = object({
port: coerce(number(), string(), Number),
host: string(),
})
Вход:
{
port: "8080",
host: "localhost"
}
После обработки:
{
port: 8080,
host: "localhost"
}
Часто данные приходят в виде строки с разделителями:
"1,2,3"
Преобразование в массив чисел:
import { coerce, array, number, string } from 'superstruct'
const NumberArray = coerce(array(number()), string(), (value) => {
return value.split(',').map(Number)
})
Такая схема широко используется при работе с query-параметрами URL.
Несмотря на гибкость, coercion может скрывать ошибки данных. Например:
Number("abc") // NaN
Если такой результат проходит дальше, структура теряет смысл.
Поэтому часто добавляется дополнительная проверка:
import { coerce, number, string, refine } from 'superstruct'
const SafeNumber = coerce(
refine(number(), 'finite', (v) => Number.isFinite(v)),
string(),
(value) => Number(value)
)
Такой подход исключает некорректные значения.
При работе с объектами трансформации могут применяться на уровне отдельных полей:
import { object, string, transform } from 'superstruct'
const User = object({
createdAt: transform(string(), (v) => new Date(v)),
name: string(),
})
Каждое поле может иметь собственную стратегию преобразования.
Одним из распространённых сценариев является нормализация входных данных в единый формат.
Пример: приведение строк к нижнему регистру:
import { string, transform } from 'superstruct'
const NormalizedString = transform(string(), (value) => {
return value.trim().toLowerCase()
})
Это особенно полезно при работе с поисковыми запросами и идентификаторами.
В сложных схемах данные проходят несколько этапов:
Пример:
const Schema = transform(
coerce(number(), string(), Number),
(value) => value * 2
)
Хотя здесь используется упрощённая цепочка, логика масштабируется на более сложные структуры.
При проектировании схем важно учитывать:
Ошибочная постановка coercion может привести к «тихим» ошибкам, когда некорректные данные становятся валидными после преобразования.
Наиболее частое применение coercion и transform — обработка данных API:
Пример:
const ApiResponse = object({
id: coerce(number(), string(), Number),
createdAt: transform(string(), (v) => new Date(v)),
payload: transform(string(), JSON.parse),
})
Такая схема позволяет из «сырого» ответа получить структурированный объект без промежуточной обработки в бизнес-логике.