CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ajv

Another JSON Schema Validator - high-performance JSON Schema validator for JavaScript and TypeScript

Pending
Overview
Eval results
Files

typescript-integration.mddocs/

TypeScript Integration

Advanced TypeScript integration with schema-to-type inference, compile-time type checking, and full type safety throughout the validation process for superior developer experience.

Capabilities

JSONSchemaType Interface

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;
}

JTDSchemaType Interface

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;
}

Generic Validation Functions

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 information

Schema Inference

Advanced 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;
}

Strict Type Checking

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

Best Practices

Schema Organization

// 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
};

Type Guard Functions

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

docs

core-validation.md

error-handling.md

index.md

jtd-schemas.md

keywords-vocabularies.md

schema-2019.md

schema-2020.md

schema-management.md

standalone-generation.md

typescript-integration.md

tile.json