typia.assert() โ throw on the first invalid field
function assert<T>(input: T): T;
function assert<T>(input: unknown): T;
function assert<T>(
input: unknown,
errorFactory?: (props: TypeGuardError.IProps) => Error,
): T;typia.assert<T> is the throwing variant of is. If input matches T, it returns input unchanged. If it doesnโt, it throws a TypeGuardError that points at exactly one failing property โ the first one it encounters.
Use it when you want an exception to propagate up the stack (HTTP request handlers, message queue consumers, boot-time config loading).
| Scenario | Pick |
|---|---|
| One field is wrong โ throw an exception | assert (you are here) |
| Just a yes/no answer | is |
| Need every field error at once (form, LLM feedback loop) | validate |
First example
import typia, { tags } from "typia";
interface User {
id: string & tags.Format<"uuid">;
age: number & tags.Type<"uint32"> & tags.Minimum<19>;
}
const user = typia.assert<User>(await loadUserFromRequest());
// โ returns the same value, typed as User
// โ throws TypeGuardError if any field is wrongIf age is 18, you get a TypeGuardError carrying:
| Field | Value |
|---|---|
method | "typia.assert" |
path | "$input.age" |
expected | "number & Minimum<19>" |
value | 18 |
The error includes a default message ready for logging, but the structured fields above are usually what you want to surface to clients.
TypeScript Source
import typia, { tags } from "typia";
import { v4 } from "uuid";
typia.assert<IMember>({
id: v4(),
email: "samchon.github@gmail.com",
age: 18, // wrong, must be greater than 19
});
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}TypeGuardError reference
class TypeGuardError<T = any> extends Error {
readonly method: string; // "typia.assert", "typia.assertEquals", โฆ
readonly path: string | undefined; // e.g. "$input.user.age"
readonly expected: string; // e.g. "string & Format<\"uuid\">"
readonly value: unknown; // the offending value
readonly description?: string; // hint when the value is `undefined`
}undefined
/**
* Error thrown when type assertion fails.
*
* Thrown by {@link assert}, {@link assertGuard}, and other assert-family
* functions when input doesn't match expected type `T`. Contains detailed
* information about the first assertion failure:
*
* - `method`: Which typia function threw (e.g., `"typia.assert"`)
* - `path`: Property path where error occurred (e.g., `"input.user.age"`)
* - `expected`: Expected type string (e.g., `"number & ExclusiveMinimum<19>"`)
* - `value`: Actual value that failed validation
*
* @template T Expected type (for type safety)
*/
export class TypeGuardError<T = any> extends Error {
/**
* Name of the typia method that threw this error.
*
* E.g., `"typia.assert"`, `"typia.assertEquals"`, `"typia.assertGuard"`.
*/
public readonly method: string;
/**
* Property path where assertion failed.
*
* Uses dot notation for nested properties. `undefined` if error occurred at
* root level.
*
* E.g., `"input.age"`, `"input.profile.email"`, `"input[0].name"`.
*/
public readonly path: string | undefined;
/**
* String representation of expected type.
*
* E.g., `"string"`, `"number & ExclusiveMinimum<19>"`, `"{ name: string; age:
* number }"`.
*/
public readonly expected: string;
/**
* Actual value that failed assertion.
*
* The raw value at the error path, useful for debugging.
*/
public readonly value: unknown;
/**
* Optional human-readable error description.
*
* Primarily for AI agent libraries or custom validation scenarios needing
* additional context. Standard assertions rely on `path`, `expected`, and
* `value` for error reporting.
*/
public readonly description?: string | undefined;
/**
* Phantom property for TypeScript type safety.
*
* Not used at runtimeโexists only to preserve generic type `T` in the type
* system. Always `undefined`.
*
* @internal
*/
protected readonly fake_expected_typed_value_?: T | undefined;
/**
* Creates a new TypeGuardError instance.
*
* @param props Error properties
*/
public constructor(props: TypeGuardError.IProps) {
// MESSAGE CONSTRUCTION
// Use custom message if provided, otherwise generate default format
super(
props.message ||
`Error on ${props.method}(): invalid type${
props.path ? ` on ${props.path}` : ""
}, expect to be ${props.expected}`,
);
// INHERITANCE POLYFILL
// Set up prototype for compatibility across different JavaScript environments
const proto = new.target.prototype;
if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto);
else (this as any).__proto__ = proto;
// ASSIGN MEMBERS
this.name = "TypeGuardError";
this.method = props.method;
this.path = props.path;
this.expected = props.expected;
this.value = props.value;
if (props.description || props.value === undefined)
this.description =
props.description ??
[
"The value at this path is `undefined`.",
"",
`Please fill the \`${props.expected}\` typed value next time.`,
].join("\n");
}
}
export namespace TypeGuardError {
/** Properties for constructing a TypeGuardError. */
export interface IProps {
/**
* Name of the typia method that threw the error.
*
* E.g., `"typia.assert"`, `"typia.assertEquals"`.
*/
method: string;
/**
* Property path where assertion failed (optional).
*
* E.g., `"input.age"`, `"input.profile.email"`.
*/
path?: undefined | string;
/**
* String representation of expected type.
*
* E.g., `"string"`, `"number & ExclusiveMinimum<19>"`.
*/
expected: string;
/** Actual value that failed assertion. */
value: unknown;
/**
* Optional human-readable error description.
*
* For AI agent libraries or custom validation needing additional context.
*/
description?: string;
/**
* Custom error message (optional).
*
* If not provided, a default message is generated from other properties.
*/
message?: undefined | string;
}
}The path uses $input.x.y notation so you can copy it into a UI error message without further processing.
Customizing the thrown error
Pass an errorFactory second argument to throw whatever your framework expects instead of TypeGuardError:
import typia from "typia";
class HttpException extends Error {
constructor(
message: string,
public readonly status: number,
) {
super(message);
}
}
typia.assert<User>(
input,
(info) =>
new HttpException(`Invalid ${info.path}: expected ${info.expected}`, 400),
);The factory receives the same TypeGuardError.IProps and its return value is thrown directly.
Strict (assertEquals)
assert allows properties not declared on T. assertEquals rejects them โ the first extra key becomes the thrown error:
typia.assert<User>({ id: "...", age: 25, sex: 1 }); // ok (sex ignored)
typia.assertEquals<User>({ id: "...", age: 25, sex: 1 }); // throws on `sex`The thrown TypeGuardError for the extra property has expected: "undefined" and path pointing at the extra key:
| Field | Value |
|---|---|
method | "typia.assertEquals" |
path | "$input.sex" |
expected | "undefined" |
value | 1 |
TypeScript Source
import typia, { tags } from "typia";
import { v4 } from "uuid";
typia.assertEquals<IMember>({
id: v4(),
email: "samchon.github@gmail.com",
age: 30,
sex: 1, // extra
});
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}Assertion guards
typia.assert<T> returns the validated value:
const user: User = typia.assert<User>(input);Thatโs convenient but it makes you assign to a new variable. If youโd rather narrow the original variable in place, use typia.assertGuard<T>. It returns nothing โ but its return type is asserts input is T, which is TypeScriptโs assertion guard syntax: when a function with that signature returns without throwing, the compiler treats the argument as narrowed to T on every subsequent line. So you keep using the same variable instead of binding a new one:
import typia from "typia";
const input: unknown = await fetchSomething();
typia.assertGuard<User>(input);
// from this line on, `input` is typed as User
console.log(input.age);typia.assertGuardEquals<T> is the strict-keys variant. Both throw TypeGuardError on mismatch.
import typia from "typia";
interface IPoint {
x: number;
y: number;
}
const input: unknown = { x: 1, y: 2 };
// PERFORM THE ASSERTION GUARD
typia.assertGuard<IPoint>(input);
// FROM NOW ON, "input" IS THE "IPoint" TYPE
input.x; // OK
input.y; // OKReusable factories
Same idea as createIs โ when you call assert<T> from many places, hoist the checker once:
import typia, { AssertionGuard } from "typia";
export const assertUser = typia.createAssert<User>();
export const assertUserEquals = typia.createAssertEquals<User>();
export const guardUser: AssertionGuard<User> = typia.createAssertGuard<User>();
export const guardUserEquals: AssertionGuard<User> =
typia.createAssertGuardEquals<User>();Annotate the variable type for createAssertGuard.
TypeScript cannot infer an asserts ... is T signature without an explicit annotation. If you write const guard = typia.createAssertGuard<User>(), the compiler will refuse to narrow input after guard(input). Always declare the variable as AssertionGuard<T>.
AssertionGuard<T> is one parameter wide, so it also hides the per-call errorFactory described below. Annotate with (input: unknown, errorFactory?: (props: TypeGuardError.IProps) => Error) => asserts input is T when you need that override.
import typia, { AssertionGuard } from "typia";
//MUST DECLARE THE VARIABLE TYPE
const explicit: AssertionGuard<number> = typia.createAssertGuard<number>();
// IF NOT, COMPILATION ERROR BE OCCURRED
const implicit = typia.createAssertGuard<number>();createAssert* factories accept an optional errorFactory that applies to every call. The function they return takes that same factory as an optional second parameter, which overrides the factory for that one call:
const assertUser = typia.createAssert<User>(
(info) => new HttpException(`Invalid ${info.path}`, 400),
);
assertUser(input); // throws HttpException
assertUser(input, (info) => new Error(`audit: ${info.path}`)); // throws ErrorDo not hand a created function straight to .map().
Array#map, forEach, and filter pass the element index as their second argument, which lands in errorFactory. TypeScript rejects rows.map(assertUser) for that reason; write rows.map((row) => assertUser(row)) instead.
Code compiled against typia 13.2.0 or earlier still type-checks the direct form, because those releases declared the returned function with one parameter. Run it on this release and the index is ignored: the failing element raises a TypeGuardError instead of the configured error. Run it on 13.2.0 and it raises TypeError: factory is not a function for every element but the first.
TypeScript Source
import typia, { tags } from "typia";
export const assertMember = typia.createAssert<IMember>();
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}What it doesnโt check
Same rules as is: user-defined classes are walked by property (no instanceof), and function-typed properties are skipped unless you turn on the functional transform flag in tsconfig.json.
Built-in classes (Date, Set<T>, Map<K, V>, Uint8Array) are checked end-to-end, including their elements:
TypeScript Source
import typia from "typia";
typia.createIs<Map<string, boolean | number | string>>();Constraints
Intersect type tags into the type to express formats, ranges, and length limits:
TypeScript Source
import typia, { tags } from "typia";
export const assertSomething = typia.createAssert<Something>();
//----
// DEFINE CUSTOM TYPE TAGS
//----
type Dollar = tags.TagBase<{
kind: "dollar";
target: "string";
value: undefined;
validate: `$input[0] === "$" && !isNaN(Number($input.substring(1).split(",").join("")))`;
}>;
type Postfix<Value extends string> = tags.TagBase<{
kind: "postfix";
target: "string";
value: Value;
validate: `$input.endsWith("${Value}")`;
}>;
type IsEven<Value extends number | bigint> = tags.TagBase<{
kind: "isEven";
target: Value extends number ? "number" : "bigint";
value: undefined;
validate: `$input % ${Numeric<2>} === ${Numeric<0>}`;
}>;
type Numeric<Value extends number | bigint> = Value extends number
? Value
: `BigInt(${Value})`;
//----
// VALIDATION
//----
interface Something {
dollar: string & Dollar;
postfix: string & Postfix<"!!!">;
isEven: number & IsEven<number>;
}Performance
Same emission strategy as is, plus the per-field error-throwing branches. Still up to 20,000ร faster than class-validator on hard-to-validate union structures, and still the only listed library that handles arbitrary unions. The structural feature matrix (recursive unions, template literals, rest tuples, etc.) lives on is ยง Performance โ same coverage, since assert is is plus error paths.
Measured on AMD Ryzen 9 7940HS, Rog Flow x13ย
Where to go next
- Just want a boolean โ
is - Want every error at once โ
validate - Constraints (formats, ranges, lengths) โ Special Tags