Skip to Content

typia.validate() โ€” collect every error at once

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

ScenarioPick
Need to render every field error to the user (form, request body)validate (you are here)
One field is wrong โ†’ throw an exceptionassert
LLM function-calling feedback loop (โ€œtell the model all its mistakesโ€)validate + LlmJson.stringify
Just a yes/no answeris

First example

hello-validate.ts
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.75

Both errors arrive in a single pass. assert would have thrown on id and stopped.

examples/src/validators/validate.ts
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 }
examples/src/validators/validateEquals.ts
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 });
examples/src/validators/createValidate.ts
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.

examples/src/validators/validate-map.ts
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:

examples/src/validators/validate-custom-tags.ts
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.

validate Function Benchmark

Measured on AMD Ryzen 9 7940HS, Rog Flow x13ย 

Where to go next

Last updated on