DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for I found 10,000x faster TypeScript validator library
Jeongho Nam
Jeongho Nam

Posted on • Updated on

I found 10,000x faster TypeScript validator library

Series of TypeScript Compiler based Libraries

  1. I made 1,000x faster TypeScript validator library
  2. I made 10x faster JSON.stringify() functions, even type safe
  3. Do not use class-validator and class-transformer, but use pure TypeScript type instead
  4. Automatic React components generator from TypeScript type

is() function benchmark

In nowaydays, TypeBox author has visited my typescript-json repository and sent a pull request containing schemas of TypeBox for benchmark. After merging the pull request and proceeding the benchmark, I'm very surprised by its amazing performance. TypeBox is faster than my library typescript-json in the is() function benchmark and its speed is even 10,000x times faster than class-validator.

Someone can doubt objectivity of the benchmark. Therefore, to ensure the objectivity, I disclose all code used in the benchmark. Below codes are being used in the benchmark program and you also can run the benchmark program just by running the npm install && npm run benchmark commands after downloading typescript-json.

Also, title of before article was I made 1,000x faster TypeScript validator library, but today's benchmark is showing that typescript-json is maximum 6,500x times faster. It's just because I added a new library class-validator in the benchmark and it is much slower than previous zod. Of course, I've continuously tuned the performance, but it just improved the performance about 2x times only.

Looking at the benchmark graph, TypeBox fails on some types like ObjectUnionImplicit, but it is originated just by limitations of JSON schema spec, that is utilized by TypeBox.

Additionally, if additionalProperties: false option which does not allow superfluous properties being used, TypeBox does not fail on such complicate union type (in typescript-json, it is called equals() function). I can't sure that using such additionalProperties: false option is an adequate solution for union type, but anway, another validator libraries never can provide such alternative solution.

Also, in the equals() function (or additionalProperties: false option), TypeBox is faster than my typescript-json as the is() function was. Looking at those benchmarks, I'm really impressed and have curious how TypeBox can be such faster even overcome AOT (Ahead of Time) compilation of typescript-json.

equals() function benchmark

How it such fast

AOT (Ahead of Time) and JIT (Just in Time) compilations.

In the previous article I made 10x faster JSON.stringify() functions, even type safe, I described that typescript-json could be faster than other validator libaries by utilizing AOT (Ahead of Time) compilation. TypeBox is also similar, but detailed skill is a little bit diffrent; JIT (Just in Time) compilation.

In the typescript-json case, it analyzes your source code in the compilation level and generates optimized validation code for the target type (ObjectSimple). Therefore, validation code is generated in the compilation level and it is called the AOT compilation.

Otherwise TypeBox case, it analyzes schema object what you've defined and generates optimized validation code for the target type (TypeBoxObjectSimple) in the runtime level. Therefore, validation code is generated in the runtime level and it is called the JIT compilation.

That is, generating optimized validation code is the reaon why typescript-json and TypeBox could be much faster than other validator libraries.

//-------------------------------------------
// SCHEMA OF TYPESCRIPT-JSON
//-------------------------------------------
import TSON from "typescript-json";

export type ObjectSimple = ObjectSimple.IBox3D;
export namespace ObjectSimple {
    export interface IBox3D {
        scale: IPoint3D;
        position: IPoint3D;
        rotate: IPoint3D;
        pivot: IPoint3D;
    }
    export interface IPoint3D {
        x: number;
        y: number;
        z: number;
    }
}

TSON.is<ObjectSimple>(input);

//-------------------------------------------
// SCHEMA OF TYPEBOX
//-------------------------------------------
import { Type } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";

const Point3D = Type.Object({
    x: Type.Number(),
    y: Type.Number(),
    z: Type.Number(),
});

const Box3D = Type.Object({
    scale: Point3D,
    position: Point3D,
    rotate: Point3D,
    pivot: Point3D,
});

const TypeBoxObjectSimple = TypeCompiler.Compile(Box3D);
TypeBoxObjectSimple.Check(input);
Enter fullscreen mode Exit fullscreen mode

Also, the reason why TypeBox is faster than typescript-json is based on how define the data schema.

Looking at the below code, than you may understand which different makes such performance gap. As you can see, typescript-json generates an independent validation function whenever new object type comes, but TypeBox does not. TypeBox generates independent validation function only when user defines it as independent.

Unfortunately, typescript-json never can follow such performance tuning by accessing object property directly like TypeBox does. Such direct accessment only can be done by TypeBox because TypeBox let users to define data schema and determine whether to utilize direct accessment or not by themselves.

//-------------------------------------------
// GENERATED CODE BY TYPESCRIPT-JSON
//-------------------------------------------
// THIS ONE LINE
const is = TSON.createIs<ObjectSimple>();

// BE CONVERTED LIKE BELOW
const is = (input) => {
    const $io0 = (input) => 
        "object" === typeof input.scale && null !== input.scale && $io1(input.scale) &&
        "object" === typeof input.position && null !== input.position && $io1(input.position) &&
        "object" === typeof input.rotate && null !== input.rotate && $io1(input.rotate) &&
        "object" === typeof input.pivot && null !== input.pivot && $io1(input.pivot);
    const $io1 = (input) =>
        "number" === typeof input.x &&
        "number" === typeof input.y &&
        "number" === typeof input.z;
    return "object" === typeof input && null !== input && $io0(input);
};

//-------------------------------------------
// TYPEBOX
//-------------------------------------------
// THIS METHOD CALL
const check = TypeBoxObjectSimple.Code();

// RETURNS BELOW FUNCTION
const check = (value) => {
    return (
        (typeof value === 'object' && value !== null && !Array.isArray(value)) &&
        (typeof value.scale === 'object' && value.scale !== null && !Array.isArray(value.scale)) &&
        (typeof value.scale.x === 'number') &&
        (typeof value.scale.y === 'number') &&
        (typeof value.scale.z === 'number') &&
        (typeof value.position === 'object' && value.position !== null && !Array.isArray(value.position)) &&
        (typeof value.position.x === 'number') &&
        (typeof value.position.y === 'number') &&
        (typeof value.position.z === 'number') &&
        (typeof value.rotate === 'object' && value.rotate !== null && !Array.isArray(value.rotate)) &&
        (typeof value.rotate.x === 'number') &&
        (typeof value.rotate.y === 'number') &&
        (typeof value.rotate.z === 'number') &&
        (typeof value.pivot === 'object' && value.pivot !== null && !Array.isArray(value.pivot)) &&
        (typeof value.pivot.x === 'number') &&
        (typeof value.pivot.y === 'number') &&
        (typeof value.pivot.z === 'number')
    );
}
Enter fullscreen mode Exit fullscreen mode

Other benchmarks

// ALLOW SUPERFLUOUS PROPERTIES
export function is<T>(input: T | unknown): input is T; // true or false
export function assertType<T>(input: T | unknown): T; // throws `TypeGuardError`
export function validate<T>(input: T | unknown): IValidation; // detailed reasons

// DO NOT ALLOW SUPERFLUOUS PROPERTIES
export function equals<T>(input: T | unknown): boolean;
export function assertEquals<T>(input: T | unknown): T;
export function validateEquals<T>(input: T | unknown): IValidation;

// DATA STRUCTURES
export interface IValidation {
    success: boolean;
    errors: IValidation.IError[];
}
export namespace IValidation {
    export interface IError {
        path: string;
        expected: string;
        value: any;
    }
}

export class TypeGuardError extends Error {
    public readonly method: string;
    public readonly path: string | undefined;
    public readonly expected: string;
    public readonly value: any;
}
Enter fullscreen mode Exit fullscreen mode

TypeBox showed amazing performance in the is() function. However, the is() function returns only boolean value which means input value is keeping type T or not. If you want to more information about the type checking, you should use other functions instead and weakpoint tof TypeBox exists there.

assertType() function benchmark

validate() function benchmark

Also, AOT compilation would be done when compiling TypeScript source code. However, JIT compilation would be done in runtime, when running JavaScript program. Therefore, when comparing the 1st validation time, result would be extremely different like below.

optimization time benchmark

Recommend TypeBox

Usage of TypeBox is similar with io-ts and zod, but it is much powerful and faster than them. Also, TypeBox can generate JSON schema very easily. Therefore, if you're looking for a validator library for new project and not suffering from legacy codes, I think TypeBox would be much better choice than io-ts and zod. TypeBox can totally replace them.

Comparing with my library typescript-json, of course, I think that my library is much easier and stronger. As you know, typescript-json does not any extra schema definition. It only requires pure TypeScript type and only one line function call. However, typescript-json needs TypeScript compiler and additional plugin configuration on tsconfig.json. Therefore, if you're developing on JavaScript environment, typescript-json never can be adopted. In such case, TypeBox would be the best alternative solution.

Next article maybe

React components can be automatically composed by using only TypeScrit type. It also requires only one like as typescript-json does.

reactia

Top comments (3)

Collapse
 
mrcaidev profile image
Yuwang Cai

10,000x... The frontend world just keeps showing me astonishing numbers

Collapse
 
intermundos profile image
intermundos

We all need this 0.001ms speed boost...

Collapse
 
pterpmnta profile image
Pedro Pimienta M.

Interesting topic, thanks.

Classic DEV Post

brain computer interface

Experimenting with brain-computer interfaces in JavaScript