Skip to content
/ tjs Public

The world's fastest and most accurate json-schema validator, with magical typescript inference.

Notifications You must be signed in to change notification settings

sberan/tjs

Repository files navigation

tjs

The world's fastest and most accurate json-schema validator, with magical typescript inference.

npm version test playground

Benchmark

100% spec compliance. 51% faster than ajv. Zero dependencies. Full TypeScript inference.

At a Glance

tjs ajv zod joi
JSON Schema compliance 100% 95% Basic None
TypeScript inference Built-in Plugin Built-in None
Dependencies 0 4+ 0 5+
Performance Fastest Fast Slow Slow

Installation

npm install tjs

Quick Start

import { schema } from 'tjs';

const User = schema({
  type: 'object',
  properties: {
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0 },
  },
  required: ['name', 'email'],
});

type User = typeof User.type; // Extract the type


// Validate with full type inference
const user = User.assert(data);
//   ^? { name: string; email: string; age?: number }

Why tjs?

  • Bridge the gap between json-schema and TypeScript types with no code duplication.
  • Types are guaranteed to be correct at compile time and run time.
  • Sticking to json-schema means your schema can be published to third parties with no translation layer.

100% JSON Schema Compliance

tjs passes the entire JSON Schema Test Suite — the official compliance benchmark:

Draft Compliance
draft-04 100% (882/882)
draft-06 100% (1170/1170)
draft-07 100% (1534/1534)
draft-2019-09 100% (1941/1941)
draft-2020-12 100% (1990/1990)
Total 100% (7517/7517)

See COMPLIANCE.md for details.

Blazing Fast

See BENCHMARKS.md for detailed performance comparison. tjs uses JIT compilation to generate optimized validation code — 51% faster than ajv overall:

Performance vs ajv (JSON Schema Test Suite):
--------------------------------------------------------------------------------
Draft          Files   Tests | tjs ns/test  ajv ns/test      Diff
--------------------------------------------------------------------------------
draft-04         43     881 |         35           74      -53%
draft-06         52    1170 |         34           66      -49%
draft-07         63    1534 |         61           75      -18%
draft-2019-09    77    1941 |         51          151      -66%
draft-2020-12    80    1990 |         50          147      -66%
--------------------------------------------------------------------------------
TOTAL            315    7516 |         48           98      -51%
--------------------------------------------------------------------------------

Format validation is where tjs really shines — up to 238x faster for complex formats:

idn-email                238x faster than ajv
date-time                9x faster than ajv
ipv6                     6x faster than ajv

Why is tjs faster?

tjs achieves its performance through several code generation optimizations that ajv doesn't implement:

Optimization tjs ajv
Format validators Character-code parsing with lookup tables Complex regex patterns (800+ chars for ipv6)
typeof caching Caches typeof result once for multi-type checks Calls typeof repeatedly
oneOf early exit Returns immediately when 2nd match found Evaluates all branches, tracks matches in arrays
Error allocation Pre-compiled error objects, zero allocation in hot path Creates error objects inline during validation

The format validators show the largest gains. For example, date-time validation in tjs uses direct character code comparisons:

// tjs: ~140 lines of simple character code checks
if (s.charCodeAt(4) !== 45) return false; // '-'
const year = (s.charCodeAt(0) - 48) * 1000 + ...

vs ajv-formats which uses regex with potential backtracking:

// ajv-formats: regex pattern
/^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i

Magical Type Inference

tjs infers TypeScript types directly from your schema — no code generation, no separate type definitions:

import { schema } from 'tjs';

const Product = schema({
  type: 'object',
  properties: {
    id: { type: 'string', format: 'uuid' },
    name: { type: 'string' },
    price: { type: 'number', minimum: 0 },
    tags: { type: 'array', items: { type: 'string' } },
    status: { enum: ['active', 'discontinued'] },
  },
  required: ['id', 'name', 'price'],
});

// Extract the inferred type
type Product = typeof Product.type;
// { id: string; name: string; price: number; tags?: string[]; status?: 'active' | 'discontinued' }

Types are inferred for:

  • All primitive types (string, number, integer, boolean, null)
  • Arrays with items and prefixItems (tuples)
  • Objects with properties, additionalProperties, patternProperties
  • Union types via anyOf, oneOf
  • Intersection types via allOf
  • Const and enum literals
  • Conditional schemas with if/then/else
  • Recursive schemas with $ref and $defs

Zero Runtime Dependencies

tjs has zero runtime dependencies. The entire library is ~25KB minified.

Compare to ajv which requires:

  • fast-deep-equal
  • json-schema-traverse
  • require-from-string
  • uri-js (which itself has dependencies)

API

schema(definition, options?)

Create a validator from a JSON Schema:

const User = schema({
  type: 'object',
  properties: {
    name: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['name'],
});

validator.validate(data)

Validate and return result with value or error:

const result = User.validate(input);

if (result.error === undefined) {
  console.log(result.value);  // Typed & coerced data
} else {
  console.log(result.error);  // Validation errors with paths
}

validator.assert(data)

Assert validity, throwing on failure. Returns coerced value:

try {
  const user = User.assert(input);  // Returns typed, coerced data
  console.log(user.name);
} catch (e) {
  console.error('Invalid:', e.message);
}

struct(properties, options?)

Ergonomic helper for object schemas:

import { struct } from 'tjs';

const User = struct({
  id: 'string',
  name: 'string',
  email: { type: 'string', format: 'email' },
  age: { type: 'integer', minimum: 0, optional: true },
  role: { enum: ['admin', 'user'], optional: true },
});

type User = typeof User.type

// Automatically infers:
// { id: string; name: string; email: string; age?: number; role?: 'admin' | 'user' }

schemaAsync(definition, options?)

Create a validator that automatically fetches remote $ref schemas:

const validator = await schemaAsync({
  $ref: 'https://json-schema.org/draft/2020-12/schema',
});

Schema Composition

Include validators directly in other schemas for easy composition:

// Define reusable schemas
const Address = schema({
  type: 'object',
  properties: {
    street: { type: 'string' },
    city: { type: 'string' },
    zip: { type: 'string' },
  },
  required: ['street', 'city'],
});

// Include in another schema
const Person = schema({
  type: 'object',
  properties: {
    name: { type: 'string' },
    address: Address,  // Use validator directly
  },
  required: ['name', 'address'],
});

type Person = typeof Person.type;
// { name: string; address: { street: string; city: string; zip?: string } }

Works in arrays, anyOf, allOf, and any other schema position:

const Team = schema({
  type: 'object',
  properties: {
    name: { type: 'string' },
    members: { type: 'array', items: Person },  // Array of Person
    headquarters: Address,
  },
  required: ['name', 'members', 'headquarters'],
});

Access the underlying schema via the .schema property:

console.log(Address.schema);
// { type: 'object', properties: { ... }, required: ['street', 'city'] }

Type Coercion

Automatically coerce values to match schema types:

const Config = schema({
  type: 'object',
  properties: {
    port: { type: 'integer' },
    debug: { type: 'boolean' },
    timeout: { type: 'number' },
  },
}, { coerce: true });

// String values are coerced to match types
const result = Config.validate({ port: '3000', debug: 'true', timeout: '30.5' });
result.value; // { port: 3000, debug: true, timeout: 30.5 }

Supported coercions:

  • Strings to numbers/integers ("42"42)
  • Strings/numbers to booleans ("true", 1true)
  • Strings to null ("", "null"null)
  • Single values to arrays ("item"["item"])

Options

interface ValidatorOptions {
  // Enable format validation (default: false for 2019-09+, true for draft-07 and earlier)
  formatAssertion?: boolean;

  // Enable content validation for contentMediaType/contentEncoding (default: false)
  contentAssertion?: boolean;

  // Pre-loaded remote schemas for $ref resolution
  remotes?: Record<string, JsonSchema>;

  // Use legacy $ref behavior where $ref ignores siblings (default: true)
  legacyRef?: boolean;

  // Enable type coercion (default: false)
  coerce?: boolean | {
    string?: boolean;
    number?: boolean;
    integer?: boolean;
    boolean?: boolean;
    null?: boolean;
    array?: boolean;
  };
}

License

MIT

About

The world's fastest and most accurate json-schema validator, with magical typescript inference.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •