Outline
typia can perform additional validation through type tags and comment tags.
When you need additional validation logic that is not supported in pure TypeScript type spec, you can use type tags and comment tags for it. For example, if you define a type with intersection symbol like number & typia.tags.Type<"uint32"> and validates it, typia will check the target numeric value is unsigned integer or not.
Also, in TypeScript (and JavaScript), writing @ character in comment is called Comment Tag and typia utilizes such comment tags for enhancing type validation logic. As you can see from below example code, typia analyzes @tagName value patterned comment tags, and generates optimal validation logic in the compilation level.
Therefore, don’t be afraid typia uses only pure TypeScript types for type validation schema. Don’t be afraid about TypeScript does not support integer type. With those type tags and comment tags, you can express every types in the world.
- Q: How to validate integer type? TypeScript does not support it
- A1: Use type tag
number & typia.tags.Type<"int32"> - A2: Write a comment tag
@type int32on the target property
- A1: Use type tag
- Q: Type Tag vs Comment Tags, which one is better
- A1: Type Tag is recommended because it is much safer and generous
- A2: Comment Tag is designed for legacy JSDoc styled projects
TypeScript Source Code
import typia, { tags } from "typia";
export const checkCustomTag = typia.createIs<CustomTag>();
interface CustomTag {
type: number & tags.Type<"uint32">;
number?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
string: string & tags.MinLength<3>;
pattern: string & tags.Pattern<"^[a-z]+$">;
/**
* Type tag can perform union type.
*
* In here case, format can be oneof `ipv4` or `ipv6` format.
*/
format: (string & (tags.Format<"ipv4"> | tags.Format<"ipv6">)) | null;
/** In the Array case, only type tag can restrict element type. */
array: Array<string & tags.Format<"uuid">> &
tags.MinItems<3> &
tags.MaxItems<100>;
/** Also, only type tag can handle map type. */
map: Map<
number & tags.Type<"uint32">,
Array<string & tags.Format<"uuid">> & tags.MinItems<1>
>;
}Type Tags
By using type tags, you can utilize additional validation logics.
Just import one of type tags from typia, and combine it with target through intersection symbol like number & typia.tags.Type<"uint32"> case. If you want to declare an union validation logic, combine | and bracket (()) symbols properly like below:
number & (Type<"uint32"> | Type<"double">)numbertype can be bothuint32anddouble
(number & Type<"int32">) | (bigint & Type<"uint64">)numberisint32bigintisuint64
(number & (Type<"int32">)| Type<"float">) | (bigint & Type<"uint64">)numbercan be bothint32andfloatbigintisuint64
Here is the entire list of type tags that typia basically supports.
For reference, when you take a mistake that choosing different target type, TypeScript compiler would block it with compilation error message. Also, if you take a mistake that placing invalid argument on the type, it would also be blocked IDE and compiler. Therefore, have a confidence when using them.
- number
number & Type<{keyword}>int32uint32uint64int64floatdouble
number & Minimum<{number}>number & Maximum<{number}>number & ExclusiveMaximum<{number}>number & ExclusiveMinimum<{number}>number & MultipleOf<{number}>
- bigint
bigint & Type<{keyword}>int64uint64
bigint & Minimum<{bigint}>bigint & Maximum<{bigint}>bigint & ExclusiveMaximum<{bigint}>bigint & ExclusiveMinimum<{bigint}>bigint & MultipleOf<{bigint}>
- string
string & MinLength<{number}>string & MaxLength<{number}>string & Pattern<{regex}>string & Format<{keyword}>bytepasswordregexuuidemailhostnameidn-emailidn-hostnameiriiri-referenceipv4ipv6uriuri-referenceuri-templateurldate-timedatetimedurationjson-pointerrelative-json-pointer
- array
Array<T> & MinItems<{number}>Array<T> & MaxItems<{number}>Array<T> & UniqueItems
Also, if you need custom validation logic, just make it by yourself referencing Customization section. It is easy to define. For such type safety and generous use case reasons even customization supporting, I recommend you to use type tags instead of comment tags, unless you are maintaining a legacy JSDoc styled project.
TypeScript Source Code
import typia, { tags } from "typia";
export const checkCustomTag = typia.createIs<CustomTag>();
interface CustomTag {
type: number & tags.Type<"uint32">;
number?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
string: string & tags.MinLength<3>;
pattern: string & tags.Pattern<"^[a-z]+$">;
/**
* Type tag can perform union type.
*
* In here case, format can be oneof `ipv4` or `ipv6` format.
*/
format: (string & (tags.Format<"ipv4"> | tags.Format<"ipv6">)) | null;
/** In the Array case, only type tag can restrict element type. */
array: Array<string & tags.Format<"uuid">> &
tags.MinItems<3> &
tags.MaxItems<100>;
/** Also, only type tag can handle map type. */
map: Map<
number & tags.Type<"uint32">,
Array<string & tags.Format<"uuid">> & tags.MinItems<1>
>;
}Comment Tags
typia supports those comment tags, too.
Here is the entire list of comment tags that typia supports.
- number
@type {string}int/int32uint/uint32int64uint64float
@minimum {number}@maximum {number}@exclusiveMinimum {number}@exclusiveMaximum {number}@multipleOf {number}
- bigint
@type uint64@minimum {bigint}@maximum {bigint}@exclusiveMinimum {bigint}@exclusiveMaximum {bigint}@multipleOf {bigint}
- string
@minLength {number}@maxLength {number}@pattern {regex}@format {keyword}bytepasswordregexuuidemailhostnameidn-emailidn-hostnameiriiri-referenceipv4ipv6uriuri-referenceuri-templateurldate-timedatetimedurationjson-pointerrelative-json-pointer
- array
@minItems {number}@maxItems {number}@uniqueItems
By the way, I do not recommend this way, because it can’t perform union numeric types, and can be used for only object property type. It can’t be used standalone, and cannot be used for element type of Array and Map even when they’re declared on object property. Also, When you declare @type int32 statement, target number type be fixed as int32 type, and never can have another numeric type by declaring union statements.
Also, those comment tags are not type safe. If you take a mistake when writing a comment tag, it will not be detected by the compiler, and will cause an error at runtime. For example, if you write a miss-spelled keyword like @type unit32, the target number type would be double type, and you can identify it just by running the program (or visiting playground website).
TypeScript Source Code
import typia, { tags } from "typia";
export const checkCustomTag = typia.createIs<CustomTag>();
interface CustomTag {
/** @type uint32 */
type: number;
number?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
/** @minLength 3 */
string: string;
pattern: string & tags.Pattern<"^[a-z]+$">;
/**
* Type tag can perform union type.
*
* In here case, format can be oneof `ipv4` or `ipv6` format.
*/
format: (string & (tags.Format<"ipv4"> | tags.Format<"ipv6">)) | null;
/** In the Array case, only type tag can restrict element type. */
array: Array<string & tags.Format<"uuid">> &
tags.MinItems<3> &
tags.MaxItems<100>;
/** Also, only type tag can handle map type. */
map: Map<
number & tags.Type<"uint32">,
Array<string & tags.Format<"uuid">> & tags.MinItems<1>
>;
}Customization
undefined
/**
* Base type for all typia validation tags.
*
* `TagBase` is the foundation for all typia type tags (constraints like
* `Minimum`, `MaxLength`, `Format`, etc.). It attaches compile-time metadata to
* TypeScript types using a phantom property pattern.
*
* The typia transformer reads these tags at compile time and generates
* appropriate runtime validation code. The tags themselves have no runtime
* presence - they exist only in the type system.
*
* This is an internal implementation detail. Use the specific tag types (e.g.,
* {@link Minimum}, {@link Format}, {@link Pattern}) rather than `TagBase`
* directly.
*
* @author Jeongho Nam - https://github.com/samchon
* @template Props Tag properties defining validation behavior and schema output
*/
export type TagBase<
Props extends TagBase.IProps<any, any, any, any, any, any>,
> = {
/**
* Compile-time marker property for typia transformer.
*
* This phantom property carries tag metadata in the type system. It is never
* assigned at runtime - it exists only for the transformer to read during
* compilation.
*/
"typia.tag"?: Props;
};
export namespace TagBase {
/**
* Configuration interface for validation tag properties.
*
* Defines all the metadata a validation tag can specify, including what types
* it applies to, how to validate values, and what to output in JSON Schema.
*
* @template Target Which primitive type(s) this tag can be applied to
* @template Kind Unique identifier for this tag type
* @template Value The constraint value specified by the user
* @template Validate The validation expression to generate
* @template Exclusive Whether this tag conflicts with others
* @template Schema Additional JSON Schema properties to output
*/
export interface IProps<
Target extends
| "boolean"
| "bigint"
| "number"
| "string"
| "array"
| "object",
Kind extends string,
Value extends boolean | bigint | number | string | undefined,
Validate extends
| string
| {
[key in Target]?: string;
},
Exclusive extends boolean | string[],
Schema extends object | undefined,
> {
/**
* Target primitive type(s) this tag applies to.
*
* The transformer will error if the tag is applied to a property of a
* different type. For example, `MinLength` targets `"string"` and cannot be
* applied to numbers.
*/
target: Target;
/**
* Unique identifier for this tag type.
*
* Used internally to identify the constraint kind. Examples: `"minimum"`,
* `"maxLength"`, `"format"`, `"pattern"`.
*/
kind: Kind;
/**
* User-configured constraint value.
*
* The value provided by the user when applying the tag. For `Minimum<5>`,
* this would be `5`. For `Format<"email">`, this would be `"email"`.
*/
value: Value;
/**
* Validation expression template.
*
* JavaScript expression string that validates the input value. Use `$input`
* as a placeholder for the actual value. The expression is inserted into
* the generated validation function.
*
* Can be a single string or an object mapping target types to different
* expressions (for tags supporting multiple types).
*
* @example
* `"5 <= $input"`; // For Minimum<5>
*
* @example
* `"$input.length <= 10"`; // For MaxLength<10>
*/
validate?: Validate;
/**
* Tag exclusivity configuration.
*
* Controls which other tags cannot be combined with this one:
*
* - `true`: No duplicate tags of the same kind allowed
* - `string[]`: List of incompatible tag kinds
* - `false` (default): No exclusivity restrictions
*
* For example, `Minimum` and `ExclusiveMinimum` are mutually exclusive -
* only one can be applied to a property.
*
* @default false
*/
exclusive?: Exclusive | string[];
/**
* Additional JSON Schema properties to output.
*
* Object containing schema properties to merge into the generated JSON
* Schema for the annotated type. For `Minimum<5>`, this would be `{
* minimum: 5 }`.
*/
schema?: Schema;
}
}Above types are supported by typia basically.
If you make a custom type tag extending typia.tags.TagBase<Props> type, and utilize it on your type with intersection symbol like number & Minimum<3>, its validation logic 3 <= $input would be inserted into the compiled JavaScript file.
Also, as you can see from the typia.tags.TagBase<Props> type, you have to specify which target type is the tag for, and need to define the tag can be compatible with others or not through exclusive options. If your custom tag has multiple target types, you can support all of those target types by defining validate property as Record<Target, string> type like Type tag case.
In the Korean proverb, there’s a word that, “it is much better to do it once than to hear it a hundred times”. Let’s see how custom type tag of typia can be defined and utilized through an example code. I’ll define three custom tag types, Postfix, Dollar and IsEven.
Here is the example code, and I think that it may easy to understand.
TypeScript Source Code
import typia, { tags } from "typia";
export const checkSomething = typia.createIs<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>;
}