Skip to content

@absoluteui/helpers/convert

Strict conversions that return undefined instead of NaN or Invalid Date.

import { ... } from '@absoluteui/helpers/convert';

Strict conversions for values that arrive as text or unknown: query strings, form fields, CSV cells, localStorage, and JSON from other systems. The to... functions return undefined for bad input instead of NaN or a guessed value; the ...OrThrow variants throw a ValidationError. The Helpers and services guide covers the shared conventions.

How do I read typed filters from a query string?

Section titled “How do I read typed filters from a query string?”

coerceFields converts a whole record by a schema and returns a Result; all bad keys come back together in error.fields, ready for a form or a 400 response.

import { coerceFields } from '@absoluteui/helpers/convert';
const query = Object.fromEntries(new URLSearchParams('page=2&active=yes&hiredAfter=2025-01-01'));
const parsed = coerceFields(query, {
page: 'integer',
active: 'boolean',
hiredAfter: { kind: 'date', optional: true },
dept: { kind: 'string', optional: true },
});
parsed.ok ? [parsed.value.page, parsed.value.active, parsed.value.dept] : []; // => [2, true, undefined]
const bad = coerceFields({ page: 'two', active: '' }, { page: 'integer', active: 'boolean' });
bad.ok ? {} : bad.error.fields; // => { page: 'Expected integer.', active: 'Required.' }

How do I parse numbers that users typed in their own format?

Section titled “How do I parse numbers that users typed in their own format?”

Pass locale to accept that locale’s group and decimal separators. Text such as '12abc' or '' is rejected rather than half-parsed, unlike parseFloat.

import { toInteger, toNumber } from '@absoluteui/helpers/convert';
toNumber('1.234,5', { locale: 'de' }); // => 1234.5
toNumber('1,234.5', { locale: 'en' }); // => 1234.5
parseFloat('12abc'); // => 12
toNumber('12abc'); // => undefined
toInteger('7.5'); // => undefined
toInteger('7.5', { rounding: 'half-even' }); // => 8

How do I accept dates without guessing the format?

Section titled “How do I accept dates without guessing the format?”

toDate accepts only a Date, epoch milliseconds, or ISO 8601 text; ambiguous forms like 01/02/2026 (January or February?) are rejected. A bare YYYY-MM-DD is UTC midnight. For calendar dates without a time, prefer helpers/date.

import { toDate, toDateOrThrow } from '@absoluteui/helpers/convert';
toDate('2026-10-05T09:00:00Z')?.toISOString(); // => '2026-10-05T09:00:00.000Z'
toDate('01/02/2026'); // => undefined
toDateOrThrow('next Friday'); // => throws HLP_CONVERT_DATE

How do I normalise a value that may be one item, many, or nothing?

Section titled “How do I normalise a value that may be one item, many, or nothing?”

toArray and toSet accept a single value, any iterable, or null/undefined, which is handy for multi-select values and API fields that are sometimes a list. Strings are kept whole, not split into characters.

import { toArray, toSet } from '@absoluteui/helpers/convert';
toArray('HR'); // => ['HR']
toArray(['HR', 'IT']); // => ['HR', 'IT']
toArray(null); // => []
toSet(['HR', 'IT', 'HR']).size; // => 2

coerce

Converts value to the named kind with the strict to* rules; undefined on failure.

function coerce<K extends CoerceKind>(value: unknown, kind: K): CoerceTypes[K] | undefined;

Example:

coerce('42', 'integer') // => 42

coerceFields

Coerces a record (e.g. form or query values) by a schema. Missing, null and blank values fail as required unless optional. Errors come back as one ValidationError (HLP_CONVERT_FIELDS) whose fields maps each bad key to a message.

function coerceFields<S extends Readonly<Record<string, FieldSpec>>>(input: unknown, schema: S): Result<Coerced<S>, ValidationError>;

Example:

coerceFields({ age: '42', active: 'yes' }, { age: 'integer', active: 'boolean' });
// ok({ age: 42, active: true })

parseIntStrict

Strict integer parse for radix 2-36: optional sign, digits only (no whitespace, prefixes or trailing text); undefined when invalid or outside the safe-integer range.

function parseIntStrict(text: string, radix?: number): number | undefined;

Example:

parseIntStrict('ff', 16) // => 255

Example:

parseIntStrict('12px') // => undefined

toArray

Array from anything: nullish -> [], arrays copied, iterables (Set, Map entries, generators) spread, strings and other values wrapped ('ab' -> ['ab']).

function toArray<T>(value: Iterable<T> | T | null | undefined): T[];

Example:

toArray(new Set([1, 2])) // => [1, 2]

Example:

toArray(5) // => [5]

toBigInt

Converts bigints, integer numbers and decimal integer strings (trimmed, up to 10,000 digits) to bigint.

function toBigInt(value: unknown): bigint | undefined;

Example:

toBigInt('-12345678901234567890') // => -12345678901234567890n

toBoolean

Boolean conversion via parseBoolean (true/false/yes/no/y/n/on/off/1/0).

function toBoolean(value: unknown, options?: ParseBooleanOptions): boolean | undefined;

Example:

toBoolean('off') // => false

toDate

Strict date conversion: valid Date (copied), epoch milliseconds, or ISO 8601 text only - YYYY-MM-DD (UTC midnight) or YYYY-MM-DDTHH:mm[:ss[.fff]] with optional Z/+/-HH:mm (no zone = local time). Ambiguous forms like '01/02/2026' are rejected.

function toDate(value: unknown): Date | undefined;

Example:

toDate('2026-09-29T10:00:00Z')?.getTime() // => 1790676000000

Example:

toDate('01/02/2026') // => undefined

toDateOrThrow

toDate or throw ValidationError (HLP_CONVERT_DATE).

function toDateOrThrow(value: unknown): Date;

Example:

toDateOrThrow('2026-01-01')

toInteger

Strict integer conversion with an explicit rounding mode.

function toInteger(value: unknown, options?: ToIntegerOptions): number | undefined;

Example:

toInteger('42') // => 42

Example:

toInteger('2.5', { rounding: 'half-even' }) // => 2

Example:

toInteger(1.5) // => undefined // rounding 'none'

toIntegerOrThrow

toInteger or throw ValidationError (HLP_CONVERT_INTEGER).

function toIntegerOrThrow(value: unknown, options?: ToIntegerOptions): number;

Example:

toIntegerOrThrow('7') // => 7

toMap

Map from a map (copied), an iterable of [key, value] pairs or a plain object; nullish -> empty map. Returns undefined for anything else.

function toMap<K, V>(value: ReadonlyMap<K, V> | Iterable<readonly [K, V]>): Map<K, V> | undefined;
function toMap<V>(value: Readonly<Record<string, V>>): Map<string, V> | undefined;
function toMap(value: unknown): Map<unknown, unknown> | undefined;

Example:

toMap({ a: 1 })?.get('a') // => 1

toNumber

Strict number conversion. Accepts finite numbers, safe-range bigints and numeric strings (trimmed; sign, decimals, exponent; - U+2212 as minus). Rejects '', whitespace, '12abc', hex, NaN and +/-Infinity.

function toNumber(value: unknown, options?: ToNumberOptions): number | undefined;

Example:

toNumber(' -1.5e3 ') // => -1500

Example:

toNumber('1.234,5', { locale: 'de' }) // => 1234.5

Example:

toNumber('12abc') // => undefined

toNumberOrThrow

toNumber or throw ValidationError (HLP_CONVERT_NUMBER).

function toNumberOrThrow(value: unknown, options?: ToNumberOptions): number;

Example:

toNumberOrThrow('3') // => 3

toPlainObject

Plain object from a plain object (shallow copy), class instance (own enumerable props), Map or iterable (including arrays) of [key, value] pairs with string/number keys. Unsafe keys (__proto__, …) from maps/pairs -> undefined.

function toPlainObject(value: unknown): Record<string, unknown> | undefined;

Example:

toPlainObject(new Map([['a', 1]])) // => { a: 1 }

toSafeInteger

Like toInteger but also requires Number.MIN_SAFE_INTEGER..MAX_SAFE_INTEGER.

function toSafeInteger(value: unknown, options?: ToIntegerOptions): number | undefined;

Example:

toSafeInteger('9007199254740993') // => undefined

toSet

Set from anything, with toArray rules (nullish -> empty set).

function toSet<T>(value: Iterable<T> | T | null | undefined): Set<T>;

Example:

toSet([1, 1, 2]).size // => 2

toStringSafe

Human-safe string for any value; never '[object Object]'. Nullish -> ''; dates -> ISO; errors -> 'Name: message'; plain objects, arrays, maps and sets -> JSON (cycles as '[Circular]', bigints as strings); objects with their own toString use it.

function toStringSafe(value: unknown): string;

Example:

toStringSafe({ a: 1n }) // => '{"a":"1"}'

Example:

toStringSafe(Symbol('x')) // => 'Symbol(x)'

CoerceTypes

Target types for coerce.

interface CoerceTypes {
string: string;
number: number;
integer: number;
'safe-integer': number;
bigint: bigint;
boolean: boolean;
date: Date;
array: unknown[];
}

ToIntegerOptions

interface ToIntegerOptions extends ToNumberOptions {
readonly rounding?: IntegerRounding;
}

ToNumberOptions

interface ToNumberOptions {
readonly locale?: string;
}

Coerced

Result shape of coerceFields.

type Coerced<S extends Readonly<Record<string, FieldSpec>>> = {
-readonly [P in keyof S]: S[P] extends {
readonly optional: true;
} ? CoerceTypes[SpecKind<S[P]>] | undefined : CoerceTypes[SpecKind<S[P]>];
};

CoerceKind

type CoerceKind = keyof CoerceTypes;

FieldSpec

Field spec for coerceFields: a kind, or a kind with optional.

type FieldSpec = CoerceKind | {
readonly kind: CoerceKind;
readonly optional?: boolean;
};

IntegerRounding

How toInteger treats fractions. 'none' rejects them; 'round' rounds half away from zero.

type IntegerRounding = 'none' | 'trunc' | 'floor' | 'ceil' | 'round' | 'half-even';