Одной из ключевых возможностей Superstruct является автоматическое преобразование входных данных к ожидаемому формату. Эта функциональность особенно полезна при работе с HTTP-запросами, параметрами форм, JSON-ответами внешних API и пользовательским вводом, где типы данных часто не соответствуют ожидаемой структуре.
Вместо ручного преобразования строк в числа, дат в объекты
Date, а булевых значений из "true" в
true, библиотека позволяет централизованно описать правила
преобразования и валидации.
Стандартная валидация проверяет уже существующее значение:
import { number, assert } from 'superstruct'
assert(42, number())
Если передать строку:
assert('42', number())
возникнет ошибка:
Expected a number, but received: "42"
Автоматическое приведение типов решает эту проблему: значение сначала преобразуется, затем проходит валидацию.
createГлавным механизмом автоматического преобразования является функция
create.
import { create, number, coerce } from 'superstruct'
Сигнатура:
create(value, struct)
Она:
import { assert, number } from 'superstruct'
assert('25', number())
Ошибка:
Expected a number, but received: "25"
coerceimport { create, coerce, number, string } from 'superstruct'
const MyNumber = coerce(
number(),
string(),
value => Number(value)
)
const result = create('25', MyNumber)
console.log(result)
Результат:
25
coerceФункция coerce создаёт структуру с преобразованием.
Сигнатура:
coerce(TargetStruct, ConditionStruct, transformer)
| Аргумент | Назначение |
|---|---|
TargetStruct |
Целевая структура |
ConditionStruct |
Тип, при котором выполняется преобразование |
transformer |
Функция преобразования |
coerceconst UserId = coerce(
number(),
string(),
value => Number(value)
)
Алгоритм:
string()transformernumber()import {
create,
coerce,
integer,
string
} from 'superstruct'
const IntFromString = coerce(
integer(),
string(),
value => parseInt(value, 10)
)
console.log(create('100', IntFromString))
import {
create,
coerce,
number,
string
} from 'superstruct'
const FloatFromString = coerce(
number(),
string(),
value => parseFloat(value)
)
console.log(create('12.55', FloatFromString))
HTTP-запросы и формы часто передают булевы значения как строки.
import {
create,
coerce,
boolean,
string
} from 'superstruct'
const BooleanFromString = coerce(
boolean(),
string(),
value => value === 'true'
)
console.log(create('true', BooleanFromString))
console.log(create('false', BooleanFromString))
Результат:
true
false
const BooleanStruct = coerce(
boolean(),
string(),
value => {
const normalized = value.toLowerCase()
return [
'true',
'1',
'yes',
'on'
].includes(normalized)
}
)
Поддерживаемые значения:
true
TRUE
1
yes
on
import {
create,
coerce,
date,
string
} from 'superstruct'
const DateFromString = coerce(
date(),
string(),
value => new Date(value)
)
const result = create(
'2025-01-10',
DateFromString
)
console.log(result)
new Date() может вернуть некорректный объект.
new Date('invalid')
Результат:
Invalid Date
При этом объект всё равно имеет тип Date.
import {
define,
object,
string,
coerce,
create
} from 'superstruct'
const ValidDate = define('ValidDate', value => {
return (
value instanceof Date &&
!Number.isNaN(value.getTime())
)
})
const DateStruct = coerce(
ValidDate,
string(),
value => new Date(value)
)
console.log(
create('2025-02-20', DateStruct)
)
import {
object,
string,
number,
boolean,
coerce,
create
} from 'superstruct'
const User = object({
id: coerce(
number(),
string(),
value => Number(value)
),
age: coerce(
number(),
string(),
value => Number(value)
),
isAdmin: coerce(
boolean(),
string(),
value => value === 'true'
),
name: string()
})
const result = create({
id: '10',
age: '25',
isAdmin: 'true',
name: 'Alex'
}, User)
console.log(result)
Результат:
{
id: 10,
age: 25,
isAdmin: true,
name: 'Alex'
}
import {
object,
string,
number,
array,
create,
coerce
} from 'superstruct'
const Product = object({
id: coerce(
number(),
string(),
Number
),
price: coerce(
number(),
string(),
Number
)
})
const Order = object({
products: array(Product)
})
const result = create({
products: [
{
id: '1',
price: '100'
},
{
id: '2',
price: '250'
}
]
}, Order)
console.log(result)
const NumberStruct = coerce(
number(),
string(),
Number
)
const BoolStruct = coerce(
boolean(),
string(),
Boolean
)
Однако такой вариант может работать неожиданно:
Boolean('false')
Результат:
true
Поскольку любая непустая строка в JavaScript является truthy-значением.
defaultedАвтоматическое приведение типов часто используется вместе с установкой значений по умолчанию.
import {
defaulted,
create,
object,
string,
number,
coerce
} from 'superstruct'
const Config = object({
port: defaulted(
coerce(
number(),
string(),
Number
),
3000
),
host: defaulted(
string(),
'localhost'
)
})
console.log(create({}, Config))
Результат:
{
port: 3000,
host: 'localhost'
}
Важно понимать порядок выполнения:
defaultedcoerceimport {
array,
number,
string,
coerce,
create
} from 'superstruct'
const NumberArray = array(
coerce(
number(),
string(),
Number
)
)
const result = create(
['1', '2', '3'],
NumberArray
)
console.log(result)
Результат:
[1, 2, 3]
nullableimport {
nullable,
number,
string,
coerce,
create
} from 'superstruct'
const NullableNumber = nullable(
coerce(
number(),
string(),
Number
)
)
console.log(create(null, NullableNumber))
console.log(create('42', NullableNumber))
import {
union,
string,
number,
coerce,
create
} from 'superstruct'
const NumericValue = union([
number(),
coerce(
number(),
string(),
Number
)
])
console.log(create('100', NumericValue))
console.log(create(200, NumericValue))
Перед приведением типов часто требуется нормализация данных.
const TrimmedNumber = coerce(
number(),
string(),
value => Number(value.trim())
)
console.log(
create(' 42 ', TrimmedNumber)
)
const PriceStruct = coerce(
number(),
string(),
value => {
return Number(
value.replace(',', '.')
)
}
)
console.log(
create('12,55', PriceStruct)
)
Number('abc')
Результат:
NaN
Тип при этом остаётся number.
import {
define,
coerce,
string,
create
} from 'superstruct'
const SafeNumber = define(
'SafeNumber',
value => {
return (
typeof value === 'number' &&
!Number.isNaN(value)
)
}
)
const NumberStruct = coerce(
SafeNumber,
string(),
Number
)
create('42', NumberStruct)
const ApiUser = {
id: '1',
age: '30',
active: 'true',
createdAt: '2025-01-10T12:00:00Z'
}
import {
object,
string,
boolean,
coerce,
create
} from 'superstruct'
const UserStruct = object({
id: coerce(
number(),
string(),
Number
),
age: coerce(
number(),
string(),
Number
),
active: coerce(
boolean(),
string(),
value => value === 'true'
),
createdAt: coerce(
date(),
string(),
value => new Date(value)
)
})
const user = create(
ApiUser,
UserStruct
)
export const NumberFromString = coerce(
number(),
string(),
Number
)
export const BooleanFromString = coerce(
boolean(),
string(),
value => value === 'true'
)
import { object } from 'superstruct'
const BaseEntity = object({
id: NumberFromString
})
const User = object({
id: NumberFromString,
age: NumberFromString
})
Автоматическое преобразование добавляет дополнительный этап обработки:
При обработке больших массивов данных это может влиять на производительность.
Плохо:
coerce(number(), string(), value => {
return expensiveOperation(value)
})
Лучше:
coerce(number(), string(), Number)
Функция преобразования должна быть:
Неправильно:
coerce(number(), string(), async value => {
return Number(value)
})
coerce не поддерживает Promise.
Плохо:
let counter = 0
const Struct = coerce(
number(),
string(),
value => {
counter++
return Number(value)
}
)
Преобразование должно быть чистой функцией.
import {
Infer,
object,
string,
number,
coerce
} from 'superstruct'
const UserStruct = object({
id: coerce(
number(),
string(),
Number
),
name: string()
})
type User = Infer<typeof UserStruct>
После преобразования:
User['id']
имеет тип:
number
import {
object,
string,
number,
boolean,
date,
coerce
} from 'superstruct'
const NumberField = coerce(
number(),
string(),
value => {
const result = Number(value)
if (Number.isNaN(result)) {
throw new Error('Invalid number')
}
return result
}
)
const BooleanField = coerce(
boolean(),
string(),
value => {
return value === 'true'
}
)
const DateField = coerce(
date(),
string(),
value => {
const result = new Date(value)
if (Number.isNaN(result.getTime())) {
throw new Error('Invalid date')
}
return result
}
)
export const UserStruct = object({
id: NumberField,
active: BooleanField,
createdAt: DateField
})