Skip to content

Instantly share code, notes, and snippets.

@tengla
Created June 5, 2025 10:58
Show Gist options
  • Save tengla/0c94c554294894f76e420d969cb9e6c5 to your computer and use it in GitHub Desktop.
Save tengla/0c94c554294894f76e420d969cb9e6c5 to your computer and use it in GitHub Desktop.
import { Type } from '@sinclair/typebox';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import { z } from 'zod';
const ajv = new Ajv({ allErrors: true, strict: false });
addFormats(ajv);
// TypeBox/AJV Schemas
const personSchemaTypeBox = Type.Object({
name: Type.String(),
age: Type.Optional(Type.Integer({ minimum: 0 })),
email: Type.Optional(Type.String({ format: 'email' })),
tags: Type.Optional(Type.Array(Type.String())),
});
const complexSchemaTypeBox = Type.Object({
user: Type.Object({
id: Type.Integer({ minimum: 1 }),
profile: Type.Object({
firstName: Type.String({ minLength: 1 }),
lastName: Type.String({ minLength: 1 }),
bio: Type.Optional(Type.String({ maxLength: 500 })),
socials: Type.Optional(Type.Array(
Type.Object({
platform: Type.Union([Type.Literal('twitter'), Type.Literal('github'), Type.Literal('linkedin')]),
url: Type.String({ format: 'uri' }),
})
)),
}),
preferences: Type.Object({
theme: Type.Union([Type.Literal('light'), Type.Literal('dark')]),
notifications: Type.Boolean(),
language: Type.String({ pattern: '^[a-z]{2}$' }),
}),
}),
metadata: Type.Object({
createdAt: Type.String({ format: 'date-time' }),
updatedAt: Type.String({ format: 'date-time' }),
version: Type.Integer({ minimum: 1 }),
}),
});
// Zod Schemas
const personSchemaZod = z.object({
name: z.string(),
age: z.number().int().min(0).optional(),
email: z.string().email().optional(),
tags: z.array(z.string()).optional(),
});
const complexSchemaZod = z.object({
user: z.object({
id: z.number().int().min(1),
profile: z.object({
firstName: z.string().min(1),
lastName: z.string().min(1),
bio: z.string().max(500).optional(),
socials: z.array(
z.object({
platform: z.enum(['twitter', 'github', 'linkedin']),
url: z.string().url(),
})
).optional(),
}),
preferences: z.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean(),
language: z.string().regex(/^[a-z]{2}$/),
}),
}),
metadata: z.object({
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
version: z.number().int().min(1),
}),
});
// Compile AJV validators
const validatePersonAjv = ajv.compile(personSchemaTypeBox);
const validateComplexAjv = ajv.compile(complexSchemaTypeBox);
// Test Data
const validPersonData = {
name: 'John Doe',
age: 30,
email: '[email protected]',
tags: ['developer', 'typescript'],
};
const invalidPersonData = {
name: '',
age: -5,
email: 'invalid-email',
tags: 'not-an-array',
};
const validComplexData = {
user: {
id: 123,
profile: {
firstName: 'Jane',
lastName: 'Smith',
bio: 'Software engineer passionate about clean code',
socials: [
{ platform: 'github', url: 'https://github.com/janesmith' },
{ platform: 'twitter', url: 'https://twitter.com/janesmith' },
],
},
preferences: {
theme: 'dark',
notifications: true,
language: 'en',
},
},
metadata: {
createdAt: '2023-01-01T00:00:00Z',
updatedAt: '2023-06-01T12:00:00Z',
version: 1,
},
};
const invalidComplexData = {
user: {
id: 0,
profile: {
firstName: '',
lastName: 'Smith',
bio: 'x'.repeat(501),
socials: [
{ platform: 'invalid', url: 'not-a-url' },
],
},
preferences: {
theme: 'purple',
notifications: 'yes',
language: 'english',
},
},
metadata: {
createdAt: 'invalid-date',
updatedAt: '2023-06-01',
version: 0,
},
};
// Benchmark utilities
function measureTime(fn: () => void, iterations: number = 100000): number {
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
return end - start;
}
function benchmark(name: string, ajvFn: () => boolean, zodFn: () => boolean, iterations: number = 100000) {
console.log(`\nπŸ”₯ ${name} Benchmark (${iterations.toLocaleString()} iterations)`);
console.log('━'.repeat(60));
// Warm up
for (let i = 0; i < 1000; i++) {
ajvFn();
zodFn();
}
const ajvTime = measureTime(ajvFn, iterations);
const zodTime = measureTime(zodFn, iterations);
const ajvOpsPerSec = Math.floor(iterations / (ajvTime / 1000));
const zodOpsPerSec = Math.floor(iterations / (zodTime / 1000));
console.log(`AJV: ${ajvTime.toFixed(2)}ms | ${ajvOpsPerSec.toLocaleString()} ops/sec`);
console.log(`Zod: ${zodTime.toFixed(2)}ms | ${zodOpsPerSec.toLocaleString()} ops/sec`);
const winner = ajvTime < zodTime ? 'AJV' : 'Zod';
const speedup = Math.max(ajvTime, zodTime) / Math.min(ajvTime, zodTime);
console.log(`πŸ† Winner: ${winner} (${speedup.toFixed(2)}x faster)`);
}
// Run benchmarks
console.log('πŸ”« AJV vs Zod Shootout - Lock and Load! πŸ”«');
console.log('=' .repeat(60));
// Simple schema - Valid data
benchmark(
'Simple Schema - Valid Data',
() => validatePersonAjv(validPersonData),
() => personSchemaZod.safeParse(validPersonData).success,
);
// Simple schema - Invalid data
benchmark(
'Simple Schema - Invalid Data',
() => validatePersonAjv(invalidPersonData),
() => personSchemaZod.safeParse(invalidPersonData).success,
);
// Complex schema - Valid data
benchmark(
'Complex Schema - Valid Data',
() => validateComplexAjv(validComplexData),
() => complexSchemaZod.safeParse(validComplexData).success,
);
// Complex schema - Invalid data
benchmark(
'Complex Schema - Invalid Data',
() => validateComplexAjv(invalidComplexData),
() => complexSchemaZod.safeParse(invalidComplexData).success,
);
// Schema compilation benchmark
console.log('\nπŸ”§ Schema Compilation Benchmark');
console.log('━'.repeat(60));
const compilationIterations = 1000;
const ajvCompilationTime = measureTime(() => {
ajv.compile(personSchemaTypeBox);
}, compilationIterations);
const zodCompilationTime = measureTime(() => {
z.object({
name: z.string(),
age: z.number().int().min(0).optional(),
email: z.string().email().optional(),
tags: z.array(z.string()).optional(),
});
}, compilationIterations);
console.log(`AJV Compilation: ${ajvCompilationTime.toFixed(2)}ms (${compilationIterations} schemas)`);
console.log(`Zod Creation: ${zodCompilationTime.toFixed(2)}ms (${compilationIterations} schemas)`);
const compileWinner = ajvCompilationTime < zodCompilationTime ? 'AJV' : 'Zod';
const compileSpeedup = Math.max(ajvCompilationTime, zodCompilationTime) / Math.min(ajvCompilationTime, zodCompilationTime);
console.log(`πŸ† Winner: ${compileWinner} (${compileSpeedup.toFixed(2)}x faster)`);
console.log('\n🎯 Battle Complete! Check the results above to see who wins each round.');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment