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>.
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.
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