Another JSON Schema Validator - high-performance JSON Schema validator for JavaScript and TypeScript
—
Advanced TypeScript integration with schema-to-type inference, compile-time type checking, and full type safety throughout the validation process for superior developer experience.
Provides compile-time type checking for JSON Schema definitions, ensuring schemas correctly describe their intended TypeScript types.
/**
* TypeScript utility type for JSON Schema with compile-time validation
* Maps TypeScript types to corresponding JSON Schema definitions
*/
type JSONSchemaType<T> = (
T extends string ? { type: "string" } & StringConstraints :
T extends number ? { type: "number" | "integer" } & NumberConstraints :
T extends boolean ? { type: "boolean" } :
T extends null ? { type: "null" } :
T extends readonly (infer U)[] ? { type: "array" } & ArraySchema<U> :
T extends Record<string, any> ? { type: "object" } & ObjectSchema<T> :
JSONSchemaDefinition<T>
) & JSONSchemaDefinition<T>;
interface StringConstraints {
minLength?: number;
maxLength?: number;
pattern?: string;
format?: string;
}
interface NumberConstraints {
minimum?: number;
maximum?: number;
exclusiveMinimum?: number;
exclusiveMaximum?: number;
multipleOf?: number;
}
interface ArraySchema<T> {
items?: JSONSchemaType<T>;
minItems?: number;
maxItems?: number;
uniqueItems?: boolean;
}
interface ObjectSchema<T> {
properties?: { [K in keyof T]-?: JSONSchemaType<T[K]> };
required?: (keyof T)[];
additionalProperties?: boolean | JSONSchemaType<any>;
}Usage Examples:
import Ajv, { JSONSchemaType } from "ajv";
// Define TypeScript interface
interface User {
id: number;
name: string;
email: string;
age?: number;
tags: string[];
profile: {
bio?: string;
verified: boolean;
};
}
// Schema with compile-time type checking
const userSchema: JSONSchemaType<User> = {
type: "object",
properties: {
id: { type: "integer" },
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 0, nullable: true },
tags: {
type: "array",
items: { type: "string" },
default: []
},
profile: {
type: "object",
properties: {
bio: { type: "string", nullable: true },
verified: { type: "boolean" }
},
required: ["verified"],
additionalProperties: false
}
},
required: ["id", "name", "email", "tags", "profile"],
additionalProperties: false
};
const ajv = new Ajv();
const validateUser = ajv.compile(userSchema);
// Type-safe validation with full IntelliSense
function processUser(data: unknown): User | null {
if (validateUser(data)) {
// data is now typed as User
return data;
}
return null;
}TypeScript utility type for JTD schemas with even tighter type integration and simpler syntax.
/**
* TypeScript utility type for JTD Schema with compile-time validation
* Provides simpler, more direct mapping from TypeScript to schema
*/
type JTDSchemaType<T> = (
T extends string ? { type: "string" } :
T extends number ? JTDNumberType :
T extends boolean ? { type: "boolean" } :
T extends readonly (infer U)[] ? { elements: JTDSchemaType<U> } :
T extends Record<string, infer V> ? { values: JTDSchemaType<V> } :
T extends Record<string, any> ? JTDObjectType<T> :
JTDUnionType<T>
) & JTDCommonProperties;
type JTDNumberType =
| { type: "float32" }
| { type: "float64" }
| { type: "int8" }
| { type: "uint8" }
| { type: "int16" }
| { type: "uint16" }
| { type: "int32" }
| { type: "uint32" };
interface JTDObjectType<T> {
properties?: { [K in keyof T]-?: JTDSchemaType<T[K]> };
optionalProperties?: { [K in keyof T]?: JTDSchemaType<T[K]> };
additionalProperties?: boolean;
}
interface JTDCommonProperties {
nullable?: boolean;
metadata?: Record<string, any>;
}Usage Examples:
import AjvJTD, { JTDSchemaType } from "ajv/dist/jtd";
// TypeScript interface
interface Product {
id: string;
name: string;
price: number;
category: "electronics" | "clothing" | "books";
features?: string[];
metadata: Record<string, string>;
}
// JTD schema with type safety
const productSchema: JTDSchemaType<Product> = {
properties: {
id: { type: "string" },
name: { type: "string" },
price: { type: "float64" },
category: { enum: ["electronics", "clothing", "books"] },
metadata: { values: { type: "string" } }
},
optionalProperties: {
features: { elements: { type: "string" } }
}
};
const ajv = new AjvJTD();
const validateProduct = ajv.compile(productSchema);
const parseProduct = ajv.compileParser<Product>(productSchema);
const serializeProduct = ajv.compileSerializer<Product>(productSchema);
// Fully type-safe operations
function handleProduct(jsonString: string): Product | null {
const product = parseProduct(jsonString);
if (product) {
// product is typed as Product with full IntelliSense
console.log(`Processing ${product.name} in ${product.category}`);
return product;
}
return null;
}Type-safe validation functions that preserve and infer types throughout the validation process.
/**
* Generic validation function with type guards
* Automatically infers return type from schema
*/
interface ValidateFunction<T = unknown> {
(data: unknown): data is T;
errors?: ErrorObject[] | null;
schema: AnySchema;
schemaEnv: SchemaEnv;
}
/**
* Compile method with generic type support
*/
compile<T = unknown>(schema: JSONSchemaType<T>): ValidateFunction<T>;
compile<T = unknown>(schema: Schema): ValidateFunction<T>;Usage Examples:
import Ajv, { JSONSchemaType } from "ajv";
interface APIRequest {
method: "GET" | "POST" | "PUT" | "DELETE";
url: string;
headers?: Record<string, string>;
body?: any;
}
const requestSchema: JSONSchemaType<APIRequest> = {
type: "object",
properties: {
method: { type: "string", enum: ["GET", "POST", "PUT", "DELETE"] },
url: { type: "string", format: "uri" },
headers: {
type: "object",
patternProperties: {
".*": { type: "string" }
},
nullable: true
},
body: { nullable: true }
},
required: ["method", "url"],
additionalProperties: false
};
const ajv = new Ajv();
const validateRequest = ajv.compile(requestSchema);
// Type-safe request handler
function handleRequest(input: unknown) {
if (validateRequest(input)) {
// input is now typed as APIRequest
console.log(`${input.method} ${input.url}`);
if (input.headers) {
Object.entries(input.headers).forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
}
if (input.body) {
console.log("Body:", input.body);
}
return input; // Return type is APIRequest
}
throw new Error("Invalid request format");
}
// Usage with full type safety
const requestData = {
method: "POST" as const,
url: "https://api.example.com/users",
headers: { "content-type": "application/json" },
body: { name: "Alice", email: "alice@example.com" }
};
const typedRequest = handleRequest(requestData);
// typedRequest has full type informationAdvanced type inference that works both ways - from types to schemas and from schemas to types.
/**
* Utility types for schema inference
*/
type InferType<T extends AnySchema> = T extends JSONSchemaType<infer U> ? U : unknown;
type InferJTDType<T extends JTDSchemaType<any>> = T extends JTDSchemaType<infer U> ? U : unknown;
/**
* Compile with automatic type inference
*/
compile<T extends AnySchema>(schema: T): ValidateFunction<InferType<T>>;Usage Examples:
import Ajv from "ajv";
const ajv = new Ajv();
// Schema with inferred types
const personSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
active: { type: "boolean" }
},
required: ["name", "age"]
} as const;
// Compile with automatic type inference
const validatePerson = ajv.compile(personSchema);
// Function parameter types are automatically inferred
function processPerson(data: unknown) {
if (validatePerson(data)) {
// TypeScript knows data has properties: name, age, active?
return {
displayName: data.name.toUpperCase(), // string methods available
isAdult: data.age >= 18, // number comparison works
status: data.active ?? false // optional property handled
};
}
return null;
}Enhanced strict mode options for even tighter type safety and validation.
/**
* Strict type checking options
*/
interface StrictTypeOptions {
strictTypes?: boolean | "log"; // Strict type checking
strictNumbers?: boolean | "log"; // Strict number validation
strictRequired?: boolean | "log"; // Strict required property checking
allowMatchingProperties?: boolean; // Allow overlapping properties
allowUnionTypes?: boolean; // Allow union type schemas
}Usage Examples:
import Ajv, { JSONSchemaType } from "ajv";
// Strict Ajv instance
const ajv = new Ajv({
strictTypes: true,
strictNumbers: true,
strictRequired: true,
allowUnionTypes: false
});
interface StrictUser {
id: number;
name: string;
email: string;
}
// Strict schema validation
const strictUserSchema: JSONSchemaType<StrictUser> = {
type: "object",
properties: {
id: { type: "integer" }, // Must be integer, not just number
name: { type: "string" },
email: { type: "string", format: "email" }
},
required: ["id", "name", "email"], // All properties required
additionalProperties: false // No extra properties allowed
};
const validateStrictUser = ajv.compile(strictUserSchema);
// This will pass strict validation
const validUser = { id: 1, name: "Alice", email: "alice@example.com" };
console.log(validateStrictUser(validUser)); // true
// This will fail strict validation
const invalidUser = { id: 1.5, name: "Bob", email: "invalid-email", extra: "not allowed" };
console.log(validateStrictUser(invalidUser)); // false// Organize schemas in modules
export interface User {
id: number;
name: string;
email: string;
}
export const userSchema: JSONSchemaType<User> = {
type: "object",
properties: {
id: { type: "integer" },
name: { type: "string" },
email: { type: "string", format: "email" }
},
required: ["id", "name", "email"],
additionalProperties: false
};
// Reuse schemas across modules
export interface UserList {
users: User[];
total: number;
}
export const userListSchema: JSONSchemaType<UserList> = {
type: "object",
properties: {
users: {
type: "array",
items: userSchema
},
total: { type: "integer", minimum: 0 }
},
required: ["users", "total"],
additionalProperties: false
};import Ajv, { JSONSchemaType, ValidateFunction } from "ajv";
// Create reusable type guard functions
function createTypeGuard<T>(schema: JSONSchemaType<T>): (data: unknown) => data is T {
const ajv = new Ajv();
const validate = ajv.compile(schema);
return (data: unknown): data is T => {
return validate(data);
};
}
// Usage
interface Config {
apiKey: string;
timeout: number;
debug?: boolean;
}
const configSchema: JSONSchemaType<Config> = {
type: "object",
properties: {
apiKey: { type: "string", minLength: 1 },
timeout: { type: "integer", minimum: 1 },
debug: { type: "boolean", nullable: true }
},
required: ["apiKey", "timeout"],
additionalProperties: false
};
const isConfig = createTypeGuard(configSchema);
// Type-safe configuration loading
function loadConfig(input: unknown): Config {
if (isConfig(input)) {
return input; // Typed as Config
}
throw new Error("Invalid configuration");
}Install with Tessl CLI
npx tessl i tessl/npm-ajv