Coercion UtilitiesAPI reference for best-effort type coercion functionsreplaceAPI Reference
Categories

Coercion Utilities

The Coercion utilities turn loose input — attribute strings, query params, form values, JSON — into the type you actually want. Each returns the target type or null when the input has no clean representation, so a failed coercion is a value you can guard with ??, never an Invalid Date, NaN, or "[object Object]" that poisons code downstream.

const page = toNumber(params.get('page')) ?? 1;
const when = toDate(input.value) ?? new Date();

Each helper is also exported as coerceBoolean, coerceNumber, coerceInteger, coerceDate, coerceDuration, coerceByteSize, coerceBytes, and coerceString for callers who think in coercion terms.

Every helper accepts an onInvalid setting. It defaults to 'null' (the failed coercion returns null, composing with ??). Pass { onInvalid: 'passthrough' } to return the original value instead, so a schema or validator can flag the bad input rather than see an erasing null — and the TypeScript return widens to include the input type (number | string for toNumber(str, { onInvalid: 'passthrough' })).

Functions

toBoolean

function toBoolean(value, { truthy, falsy, loose = false, onInvalid = 'null' } = {})

Coerces a value to a boolean, or null when it recognizes no boolean reading. Booleans pass through, numbers read by zero-ness, and strings match a generous set case-insensitively: true, t, yes, y, on, enabled read as true, and false, f, no, n, off, disabled, null, undefined, nan read as false, alongside numeric strings by their value. Anything else returns null.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration
Options
Name Type Default Description
truthy string | string[] — Extra tokens to treat as true for this call, winning over the config vocabulary
falsy string | string[] — Extra tokens to treat as false for this call, winning over the config vocabulary and a truthy match
loose boolean false Coerce unrecognized input via native truthiness instead of returning null
onInvalid ‘null’ | ‘passthrough’ ‘null’ How an unrecognized value resolves (loose takes precedence when both are set)

Returns

true, false, or null if unrecognized. Never null under loose.

Example

import { toBoolean } from '@semantic-ui/utils';
console.log(toBoolean('yes')); // true
console.log(toBoolean('off')); // false
console.log(toBoolean('false')); // false (not the native Boolean('false') === true)
console.log(toBoolean('1')); // true
// unrecognized input is null, so it composes with ??
console.log(toBoolean('banana')); // null
console.log(toBoolean('banana') ?? false); // false
// loose falls back to native truthiness, never null
console.log(toBoolean('banana', { loose: true })); // true
// extend the falsy set without losing the defaults
console.log(toBoolean('nope', { falsy: ['nope'] })); // false

toBoolean.config

toBoolean.config holds the recognized vocabulary (truthy, falsy) and the defaults for loose and onInvalid. Edit it once at app boot and every call inherits it, so a locale or domain adds its own spellings in one place instead of passing truthy/falsy on every call. Per-call settings still win over it.

import { toBoolean } from '@semantic-ui/utils';
// teach it another language, once
toBoolean.config.truthy.push('oui', 'sí');
toBoolean.config.falsy.push('non', 'nein');
console.log(toBoolean('oui')); // true
console.log(toBoolean('non')); // false
// flip a default for every call
toBoolean.config.onInvalid = 'passthrough';
console.log(toBoolean('banana')); // 'banana'

toNumber

function toNumber(value, { onInvalid = 'null' } = {})

Coerces a value to a finite number, or null when it can’t be converted. Numeric strings parse via Number (so hex, scientific, and surrounding whitespace are fine), booleans read as 1 and 0, and NaN, Infinity, blanks, unit-tagged strings, arrays, and objects return null. It never hands back NaN or Infinity.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration (onInvalid, see above)

Returns

The finite number, or null if unconvertible.

Example

import { toNumber } from '@semantic-ui/utils';
console.log(toNumber('3.14')); // 3.14
console.log(toNumber(' 42 ')); // 42
console.log(toNumber('5px')); // null
console.log(toNumber('1e999')); // null (overflow to Infinity)
console.log(toNumber(true)); // 1
console.log(toNumber('abc') ?? 0); // 0

toInteger

function toInteger(value, { onInvalid = 'null' } = {})

Coerces a value to an integer by truncating toward zero, or null when it’s unconvertible or non-finite. Equivalent to toNumber followed by a truncation, so Infinity joins the null bucket. Under { onInvalid: 'passthrough' } it returns the original value, never a truncated NaN.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration (onInvalid, see above)

Returns

The truncated integer, or null if unconvertible.

Example

import { toInteger } from '@semantic-ui/utils';
console.log(toInteger('3.9')); // 3
console.log(toInteger('-3.9')); // -3
console.log(toInteger(42.7)); // 42
console.log(toInteger(Infinity)); // null

toDate

function toDate(value, { onInvalid = 'null', epoch = 'milliseconds' } = {})

Coerces a value to a Date, or null when it can’t be parsed — never an Invalid Date. Accepts a Date, a number read as epoch milliseconds, an ISO-8601 string, any value that hands its instant over through toJSDate(), or a Temporal value holding an instant, an Instant or a ZonedDateTime. A PlainDate, a PlainTime or a Duration holds no instant and reads as null. Ambiguous or locale-dependent spellings return null rather than a guessed date: bare years, slash and text dates, and day overflow all reject, so '01/15/2024' and '2024-02-30' never become a wrong Date. A zoneless datetime resolves in the ambient timezone (the user’s zone on the client) and is returned as a UTC instant, the same as native new Date on an <input type="datetime-local"> value. A date-only string is read as UTC, and a timestamp passed as a string is rejected, so pass a number for epoch ms.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration
Options
Name Type Default Description
epoch ‘milliseconds’ | ‘seconds’ ‘milliseconds’ How a number reads. Pass 'seconds' for unix-second timestamps (a JWT exp), where the millisecond reading would produce a valid but wrong 1970 date
onInvalid ‘null’ | ‘passthrough’ ‘null’ How a failed coercion resolves (see above)

Returns

The Date, or null if unparseable.

Example

import { toDate } from '@semantic-ui/utils';
console.log(toDate('2024-01-01')); // Date for 2024-01-01
console.log(toDate(1700000000000)); // Date from epoch ms
console.log(toDate(payload.exp, { epoch: 'seconds' })); // Date from a unix-second JWT exp
console.log(toDate('01/15/2024')); // null (ambiguous format)
console.log(toDate('banana')); // null (never an Invalid Date)
console.log(toDate(Temporal.Now.instant())); // Date for the same instant
console.log(toDate(Temporal.ZonedDateTime.from('2026-09-06T23:30:00+09:00[Asia/Tokyo]'))); // Date for the same instant
console.log(toDate(Temporal.PlainDate.from('2026-09-06'))); // null (a calendar day holds no instant)
console.log(toDate({ toJSDate: () => new Date(0) })); // Date at the epoch, through toJSDate()
const when = toDate(userInput) ?? new Date();

toDuration

function toDuration(value, { onInvalid = 'null' } = {})

Coerces a duration expression to milliseconds, or null when it reads as no duration at all. A number is already milliseconds, and a string takes one number followed by one optional unit, case-insensitively, with an optional space between the two: '5s', '1.5h', '10 minutes', '2hrs', '300msecs', '.5d'. Leave the unit off and the number reads as milliseconds, so '1500' and 1500 agree.

The sign is kept, so '-1.5h' is -5400000. Compound expressions like '1h 30m' are a different grammar and return null rather than a partial reading, as do unknown units, bare numbers with trailing junk, and exponent notation.

formatDuration is the inverse, printing milliseconds back in this grammar (300000 as '5m'), so a config can read '5m' on the way in and show '5m' on the way out.

Units

Unit Accepted spellings
Milliseconds ms, msec, msecs, millisecond, milliseconds
Seconds s, sec, secs, second, seconds
Minutes m, min, mins, minute, minutes
Hours h, hr, hrs, hour, hours
Days d, day, days
Weeks w, week, weeks

Every unit here is a fixed span. Years and months are absent because neither has one: a year is a judgment call (the ms package reads it as 365.25 days) and a month has no length without a calendar date to anchor it. Name your own value in toDuration.config.units rather than inheriting someone else’s guess.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration (onInvalid, see above)

Returns

The duration in milliseconds, or null if unreadable.

Example

import { toDuration } from '@semantic-ui/utils';
console.log(toDuration('5s')); // 5000
console.log(toDuration('1.5h')); // 5400000
console.log(toDuration('10 minutes')); // 600000
console.log(toDuration(1500)); // 1500 (already milliseconds)
console.log(toDuration('1500')); // 1500 (no unit reads as milliseconds)
console.log(toDuration('1h 30m')); // null (compound expressions are a different grammar)
setTimeout(retry, toDuration(config.retryAfter) ?? 1000);

toDuration.config

toDuration.config.units holds the milliseconds per unit, keyed by the lowercase spelling accepted after the number. Edit it once at app boot to teach the grammar a unit your domain uses, and every call inherits it.

import { toDuration } from '@semantic-ui/utils';
toDuration.config.units.y = 365 * 24 * 60 * 60 * 1000;
toDuration.config.units.years = toDuration.config.units.y;
console.log(toDuration('1y')); // 31536000000

toByteSize

function toByteSize(value, { onInvalid = 'null', base } = {})

Coerces a byte size expression to a whole number of bytes, or null when it reads as no size at all. A number is already bytes, and a string takes one number followed by one optional unit, case-insensitively, with an optional space between the two: '10mb', '1.5 KB', '2 GiB', '.5gb'. Leave the unit off and the number reads as bytes, so '1500' and 1500 agree. The same grammar as toDuration, for the other quantity a config file spells with a unit.

The built-in units are abbreviations only, the grammar of the ecosystem’s bytes() package, where toDuration reads words because ms() does. Sizes in a config are spelled short, and every importer pays for each spelling in the table, so '10 megabytes' and '512k' are one toByteSize.config line rather than a built-in.

kb through pb scale by base, 1024 unless configured, because that is what every config that accepts '10mb' already means (nginx, Docker, upload limits). The IEC spellings kib through pib are 1024 by definition and never move. A byte is indivisible, so the result rounds to the nearest whole byte, which also folds the float noise a decimal multiplier leaves ('1.1mb' at base 1000 is 1100000, not 1100000.0000000002).

The sign is kept, so '-2mb' is a delta. Bits are not bytes and '10mbit' returns null, as do compound forms like '1mb 512kb', unknown units, and exponent notation. Matching is case-insensitive, so 'Mb' reads as megabytes, not megabits.

Units

Unit Scales by
b 1
kb base¹
mb base²
gb base³
tb base⁴
pb base⁵
kib, mib, gib, tib, pib 1024ⁿ, always

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration
Options
Name Type Default Description
base number toByteSize.config.base (1024) What kb/mb/gb scale by for this call, 1024 or 1000
onInvalid string 'null' How a failed coercion resolves, see above

Returns

The size in bytes, or null if unreadable.

Example

import { toByteSize, byteLength } from '@semantic-ui/utils';
console.log(toByteSize('10mb')); // 10485760
console.log(toByteSize('1.5 KB')); // 1536
console.log(toByteSize('10mib')); // 10485760 (whatever base says)
console.log(toByteSize('10mb', { base: 1000 })); // 10000000
console.log(toByteSize(1500)); // 1500 (already bytes)
console.log(toByteSize('1h')); // null (a duration is a different quantity)
const maxUpload = toByteSize(config.maxUpload) ?? toByteSize('10mb');
if (byteLength(body) > maxUpload) { reject(); }

toByteSize.config

toByteSize.config.base is what the ambiguous units scale by, and toByteSize.config.units holds the exponent of that base per unit, keyed by the lowercase spelling accepted after the number. Values are exponents rather than multipliers so a new spelling is one line and follows the base. toByteSize.config.iecUnits is the same shape over 1024 and is never moved by base. Edit any of them once at app boot and every call inherits it.

import { toByteSize } from '@semantic-ui/utils';
toByteSize.config.base = 1000; // SI everywhere, to match a macOS-style display
toByteSize.config.units.m = 2; // the nginx and Docker spelling
toByteSize.config.units.megabytes = 2;
toByteSize.config.units.eb = 6;
console.log(toByteSize('1kb')); // 1000
console.log(toByteSize('10m')); // 10000000
console.log(toByteSize('1eb')); // 1000000000000000000

toBytes

function toBytes(value, { onInvalid = 'null' } = {})

Coerces a value to a Uint8Array, or null when it holds no bytes. A string reads as its UTF-8 text, a Uint8Array returns as the same reference, an ArrayBuffer or any other typed array or DataView becomes a view over the same memory bounded to the view (no copy), and an array of integers 0..255 is copied into a new array.

A string is always text, never an encoding. Decoding is fromBase64’s job, and the spelling that returns bytes from base64 is fromBase64(s, { as: 'bytes' }). A bare number returns null rather than reading as a length, and an array holding a non-byte ([300], [1.5]) returns null rather than wrapping it, since neither is a clean reading.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration (onInvalid, see above)

Returns

The Uint8Array, or null if the value holds no bytes.

Example

import { toBytes } from '@semantic-ui/utils';
console.log(toBytes('héllo')); // Uint8Array [104, 195, 169, 108, 108, 111]
console.log(toBytes(new Float32Array([1]))); // Uint8Array over the same 4 bytes
console.log(toBytes([1, 2, 3])); // Uint8Array [1, 2, 3]
console.log(toBytes([1, 2, 300])); // null (300 is not a byte)
console.log(toBytes(5)); // null (a number is not a length)
await crypto.subtle.digest('SHA-256', toBytes(payload));

toString

function toString(value, { loose = false, onInvalid = 'null' } = {})

Coerces a value to a string, or null when there is no faithful string form. Strings pass through and a finite number, a boolean, or a bigint goes through String(). Objects, arrays, functions, symbols, non-finite numbers, and nullish return null, so a String field never silently stores "[object Object]" or "NaN". Under loose, objects and arrays render with JSON for display output, still returning null on unserializable input rather than throwing.

Parameters

Name Type Description
value unknown The value to coerce
settings object Optional configuration
Options
Name Type Default Description
loose boolean false Render objects and arrays with JSON instead of returning null
onInvalid ‘null’ | ‘passthrough’ ‘null’ How a value with no faithful string form resolves

Returns

The string, or null if there is no faithful string form.

The loose output is a display string, not a round-trippable serialization — JSON drops undefined and functions and rewrites Date and NaN, so it should not be fed back through toDate or toNumber.

Example

import { toString } from '@semantic-ui/utils';
console.log(toString(42)); // '42'
console.log(toString(true)); // 'true'
console.log(toString({ a: 1 })); // null
console.log(toString({ a: 1 }, { loose: true })); // '{"a":1}'
console.log(toString([1, 2], { loose: true })); // '[1,2]'
Previous
Cloning
Next
Colors