typia.validate() โ collect every error at once
function validate<T>(input: T): IValidation<T>;
function validate<T>(input: unknown): IValidation<T>;typia.validate<T> walks the entire structure and reports every field that doesnโt match T, instead of stopping at the first one like assert.
| Scenario | Pick |
|---|---|
| Need to render every field error to the user (form, request body) | validate (you are here) |
| One field is wrong โ throw an exception | assert |
| LLM function-calling feedback loop (โtell the model all its mistakesโ) | validate + LlmJson.stringify |
| Just a yes/no answer | is |
First example
import typia, { tags } from "typia";
interface User {
id: string & tags.Format<"uuid">;
age: number & tags.Type<"uint32">;
}
const result = typia.validate<User>(input);
if (result.success) {
// result.data is User
console.log(result.data.id);
} else {
// result.errors is IValidation.IError[]
for (const e of result.errors) {
console.log(`${e.path}: expected ${e.expected}, got`, e.value);
}
}If id is the literal 5 and age is 20.75, you get:
$input.id : expected "string & Format<\"uuid\">", got 5
$input.age: expected "number & Type<\"uint32\">", got 20.75Both errors arrive in a single pass. assert would have thrown on id and stopped.
TypeScript Source
import typia, { tags } from "typia";
const res: typia.IValidation<IMember> = typia.validate<IMember>({
id: 5, // wrong, must be string (uuid)
age: 20.75, // wrong, not integer
email: "samchon.github@gmail.com",
});
if (res.success === false) console.log(res.errors);
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}The IValidation<T> shape
type IValidation<T> = IValidation.ISuccess<T> | IValidation.IFailure;
namespace IValidation {
interface ISuccess<T> {
success: true;
data: T;
}
interface IFailure {
success: false;
data: unknown;
errors: IError[];
}
interface IError {
path: string; // "$input.user.age"
expected: string; // "number & Minimum<19>"
value: unknown; // the bad value
description?: string;
}
}undefined
export function validate<T>(input: T): IValidation<T>;
export function validate<T>(input: unknown): IValidation<T>;The two halves of the union share a success boolean โ TypeScript narrows the rest accordingly when you check it:
const result: IValidation<string> = typia.validate<string>(something);
if (result.success) {
result.data; // string
} else {
result.errors; // IValidation.IError[]
}This pattern is called a discriminated unionย , and itโs the cleanest way to consume validateโs return value.
Strict (validateEquals)
validate allows extra properties on objects. validateEquals reports each one as its own error:
const r = typia.validateEquals<User>({ id: "uuidโฆ", age: 25, sex: 1 });
// r.success === false
// r.errors[0] = { path: "$input.sex", expected: "undefined", value: 1 }TypeScript Source
import typia, { tags } from "typia";
const res: typia.IValidation<IMember> = typia.validateEquals<IMember>({
age: 30,
email: "samchon.github@gmail.com",
id: "something", // wrong, must be string (uuid)
sex: 1, // extra property
});
if (res.success === false) console.log(res.errors);
interface IMember {
id: string & tags.Format<"uuid">;
email: string & tags.Format<"email">;
age: number &
tags.Type<"uint32"> &
tags.ExclusiveMinimum<19> &
tags.Maximum<100>;
}Reusable factories
const validateUser = typia.createValidate<User>();
const validateUserEquals = typia.createValidateEquals<User>();Each factory returns a function (input: unknown) => IValidation<T> that you can call from anywhere โ the per-type code is emitted once at the factory call site.
createValidate and createValidateEquals go a step further: the returned function also implements the Standard Schemaย interface (StandardSchemaV1<T, T>). That makes the validator a drop-in for any library that accepts Standard Schema, like up-fetchย :
import typia from "typia";
import { up } from "up-fetch";
interface ISmallTodo {
userId: number;
/** @maximum 5 */
id: number;
title: string;
completed: boolean;
}
const upfetch = up(fetch);
const schema = typia.createValidate<ISmallTodo>();
// id โค 5 โ success
await upfetch("https://jsonplaceholder.typicode.com/todos/1", { schema });
// id = 10 โ validation failure surfaced by up-fetch
await upfetch("https://jsonplaceholder.typicode.com/todos/10", { schema });TypeScript Source
import typia, { tags } from "typia";
export const validateMember = typia.createValidate<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 the rest of the validator family โ see is ยง What it doesnโt check. User-defined classes are walked by property, function types are skipped unless functional is on, native classes (Date, Set, Map, Uint8Array) are inspected end-to-end.
TypeScript Source
import typia from "typia";
typia.createIs<Map<string, boolean | number | string>>();Constraints
Intersect type tags (formats, ranges, lengths) into the type. The error expected field repeats the same intersection syntax, so users see exactly what was required:
TypeScript Source
import typia, { tags } from "typia";
export const validateSomething = typia.createValidate<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
validate does the most work โ it walks the entire structure even after finding errors, and collects each one with path and expected-type strings. Itโs still up to 20,000ร faster than the JS-ecosystem alternatives on union-heavy types, and still the only one that handles arbitrary unions. The full structural feature matrix is on is ยง Performance โ same coverage, since validate extends is with error collection.
Measured on AMD Ryzen 9 7940HS, Rog Flow x13ย
Where to go next
- Throw on first failure โ
assert - Yes/no check โ
is - Constraints (formats, ranges, lengths) โ Special Tags
- LLM auto-correction loop โ LlmJson.stringify