В библиотеке Superstruct coercion используется для преобразования входных данных перед этапом валидации. Это особенно важно при работе с внешними источниками данных:
Coercion позволяет:
null и undefined;Основная функция для создания coercion-структур —
coerce.
coerce(struct, condition, coercer)
Аргументы:
| Аргумент | Описание |
|---|---|
struct |
итоговая структура после преобразования |
condition |
структура-предикат для запуска coercion |
coercer |
функция преобразования |
import { coerce, number, string, create } fr om 'superstruct'
const NumberFromString = coerce(
number(),
string(),
value => Number(value)
)
const result = create('42', NumberFromString)
console.log(result)
Результат:
42
Последовательность выполнения:
string();number().Функция assert() только валидирует данные.
assert('42', NumberFromString)
Если coercion не запускается или результат невалиден — выбрасывается ошибка.
Функция create():
Поэтому coercion чаще всего используется именно вместе с
create().
import { coerce, integer, string, create } from 'superstruct'
const IntFromString = coerce(
integer(),
string(),
value => parseInt(value, 10)
)
console.log(create('100', IntFromString))
import { coerce, number, string, create } from 'superstruct'
const FloatFromString = coerce(
number(),
string(),
value => parseFloat(value)
)
console.log(create('10.55', FloatFromString))
Проблема:
Number('abc')
Вернёт:
NaN
Без дополнительной проверки это может привести к ошибкам.
Правильный вариант:
const SafeNumber = coerce(
number(),
string(),
value => {
const result = Number(value)
if (Number.isNaN(result)) {
return 0
}
return result
}
)
Частая задача при работе с query-параметрами и .env.
const BooleanFromString = coerce(
boolean(),
string(),
value => value === 'true'
)
Использование:
create('true', BooleanFromString)
import { coerce, boolean, string } from 'superstruct'
const SmartBoolean = coerce(
boolean(),
string(),
value => {
const normalized = value.toLowerCase().trim()
return [
'true',
'1',
'yes',
'on'
].includes(normalized)
}
)
import { coerce, date, number } from 'superstruct'
const DateFromTimestamp = coerce(
date(),
number(),
value => new Date(value)
)
import { coerce, date, string } from 'superstruct'
const DateFromString = coerce(
date(),
string(),
value => new Date(value)
)
Использование:
const result = create(
'2025-01-01',
DateFromString
)
Объект Date может быть невалидным.
new Date('wrong')
Поэтому требуется дополнительная проверка.
const SafeDate = coerce(
date(),
string(),
value => {
const result = new Date(value)
if (Number.isNaN(result.getTime())) {
return new Date(0)
}
return result
}
)
import {
coerce,
string,
optional
} from 'superstruct'
const DefaultName = coerce(
string(),
optional(string()),
value => value ?? 'Anonymous'
)
import {
object,
string,
coerce,
create
} from 'superstruct'
const User = object({
name: coerce(
string(),
string(),
value => value.trim()
)
})
import {
object,
string,
number,
coerce
} from 'superstruct'
const User = object({
age: coerce(
number(),
string(),
value => Number(value)
),
name: coerce(
string(),
string(),
value => value.trim()
)
})
Использование:
const result = create({
age: '25',
name: ' Alex '
}, User)
console.log(result)
Результат:
{
age: 25,
name: 'Alex'
}
const TrimmedString = coerce(
string(),
string(),
value => value.trim()
)
const LowercaseString = coerce(
string(),
string(),
value => value.toLowerCase()
)
const UppercaseString = coerce(
string(),
string(),
value => value.toUpperCase()
)
const EmailStruct = coerce(
string(),
string(),
value => value
.trim()
.toLowerCase()
)
const PhoneStruct = coerce(
string(),
string(),
value => value.replace(/\D/g, '')
)
Пример:
create('+7 (777) 123-45-67', PhoneStruct)
Результат:
77771234567
import {
array,
number,
string,
coerce
} from 'superstruct'
const NumberArray = coerce(
array(number()),
array(string()),
values => values.map(Number)
)
const SafeNumberArray = coerce(
array(number()),
array(string()),
values => {
return values.map(value => {
const num = Number(value)
return Number.isNaN(num)
? 0
: num
})
}
)
Иногда требуется преобразовать структуру полностью.
const User = coerce(
object({
id: number(),
name: string()
}),
object({
id: string(),
name: string()
}),
value => ({
...value,
id: Number(value.id),
name: value.name.trim()
})
)
Типичная задача в backend-разработке.
Исходные данные:
{
page: '10',
lim it: '20',
active: 'true'
}
Преобразование:
import {
object,
number,
boolean,
string,
coerce
} fr om 'superstruct'
const Query = object({
page: coerce(
number(),
string(),
Number
),
lim it: coerce(
number(),
string(),
Number
),
active: coerce(
boolean(),
string(),
value => value === 'true'
)
})
const Env = object({
PORT: coerce(
number(),
string(),
Number
),
DEBUG: coerce(
boolean(),
string(),
value => value === 'true'
)
})
Coercion и refine() отлично комбинируются.
import {
coerce,
refine,
number,
string
} from 'superstruct'
const PositiveNumber = refine(
coerce(
number(),
string(),
Number
),
'PositiveNumber',
value => value > 0
)
Важная особенность:
condition;const CleanNumber = coerce(
number(),
string(),
value => {
return Number(
value
.trim()
.replace(',', '.')
)
}
)
Пример:
create(' 10,5 ', CleanNumber)
Результат:
10.5
import {
nullable,
string,
coerce
} from 'superstruct'
const NullableString = coerce(
nullable(string()),
string(),
value => {
if (value === '') {
return null
}
return value
}
)
import {
optional,
string,
coerce
} from 'superstruct'
const OptionalString = coerce(
optional(string()),
string(),
value => {
return value === ''
? undefined
: value
}
)
Повторяющиеся coercion-структуры удобно выносить в фабрики.
import {
coerce,
number,
string
} from 'superstruct'
function numberFromString() {
return coerce(
number(),
string(),
value => Number(value)
)
}
Использование:
const User = object({
age: numberFromString()
})
function trimString() {
return coerce(
string(),
string(),
value => value.trim()
)
}
const User = object({
profile: object({
age: coerce(
number(),
string(),
Number
),
email: coerce(
string(),
string(),
value => value.trim().toLowerCase()
)
})
})
Пустые строки часто требуют специальной логики.
const EmptyToNull = coerce(
nullable(string()),
string(),
value => {
return value.trim() === ''
? null
: value
}
)
const RoleStruct = coerce(
string(),
string(),
value => value.toLowerCase()
)
Coercion подходит для предварительной очистки данных.
const SafeHtml = coerce(
string(),
string(),
value => value
.replace(/<[^>]*>/g, '')
.trim()
)
Неправильно:
assert(data, Struct)
Правильно:
create(data, Struct)
Плохо:
value => Number(value)
Лучше:
value => {
const result = Number(value)
return Number.isNaN(result)
? 0
: result
}
Нежелательно:
value.id = Number(value.id)
return value
Лучше:
return {
...value,
id: Number(value.id)
}
Coercion выполняется при каждой валидации.
Дорогие операции внутри coercion-функций могут существенно влиять на производительность:
value => heavyOperation(value)
Рекомендуется:
export const Email = coerce(
string(),
string(),
value => value.trim().toLowerCase()
)
export const transforms = {
number: value => Number(value),
boolean: value => value === 'true',
trim: value => value.trim()
}
Небольшие преобразования легче:
Плохо:
value => {
// 100 строк логики
}
Лучше:
value => normalize(
sanitize(
transform(value)
)
)