Ajv
GitHub概要
ajv-validator/ajv
The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)
スター14,418
ウォッチ109
フォーク930
作成日:2015年5月19日
言語:TypeScript
ライセンス:MIT License
トピックス
ajvjson-schemavalidator
スター履歴
データ取得日時: 2025/10/22 09:49
ライブラリ
Ajv
概要
Ajvは「Another JSON Schema validator」として知られる、世界最速のJSON Schemaバリデーションライブラリです。JSON Schema draft-04/06/07/2019-09/2020-12およびJSON Type Definition (RFC8927)をサポートし、Node.jsとブラウザの両方で動作します。高いパフォーマンスとコンパイル時最適化を特徴とし、2025年現在もJavaScriptエコシステムにおけるJSON Schemaバリデーションのデファクトスタンダードです。
詳細
Ajv 9は2025年現在の最新安定版で、JSON Schemaの全バージョンを包括的にサポートしています。「A」はAnother、「j」はJSON、「v」はvalidatorを表し、シンプルな名前でありながら業界標準の機能を提供します。12k+のGitHubスターを獲得し、express-validator、fastify、JSON Schema Editorなど多くのライブラリで内部的に使用されています。バリデーション実行時のパフォーマンスを最優先に設計され、コンパイル済みバリデーション関数による高速実行を実現。
主な特徴
- 最速のパフォーマンス: コンパイル済みバリデーション関数による最適化実行
- 完全なJSON Schema対応: 全ドラフトバージョンと最新仕様への準拠
- ゼロ依存関係: 外部ライブラリに依存しない軽量設計
- TypeScript完全対応: 型安全なバリデーションとコンパイル時チェック
- カスタムキーワード: 独自のバリデーションルールの拡張可能
- 詳細なエラー情報: デバッグに有用な包括的エラーレポート
メリット・デメリット
メリット
- 業界最高レベルのバリデーション速度
- JSON Schema標準への完全準拠で相互運用性が高い
- 豊富なコミュニティとエコシステム
- TypeScriptとの優れた統合
- 多様な実行環境(Node.js、ブラウザ、CDN)対応
- 詳細で理解しやすいエラーメッセージ
デメリット
- 学習コストがやや高い(JSON Schema仕様の理解が必要)
- 複雑なスキーマでの設定時間
- カスタムキーワード定義の複雑性
- 大規模スキーマでのメモリ使用量
- 非JSON形式データには適用できない
- TypeScript型推論は別ライブラリが必要
参考ページ
書き方の例
インストールと基本セットアップ
# Ajvのインストール
npm install ajv
yarn add ajv
pnpm add ajv
bun add ajv
# TypeScript定義も含まれています
# 追加のformat対応(オプション)
npm install ajv-formats
基本的なスキーマ定義とバリデーション
const Ajv = require("ajv");
const ajv = new Ajv(); // オプション: {allErrors: true}
// 基本的なJSON Schemaの定義
const schema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number", minimum: 0 },
email: { type: "string", format: "email" }
},
required: ["name", "age"],
additionalProperties: false
};
// スキーマのコンパイル
const validate = ajv.compile(schema);
// データのバリデーション
const userData = { name: "田中太郎", age: 30, email: "[email protected]" };
const valid = validate(userData);
if (valid) {
console.log("バリデーション成功");
} else {
console.log("バリデーションエラー:", validate.errors);
/*
[
{
instancePath: "/email",
schemaPath: "#/properties/email/format",
keyword: "format",
params: { format: "email" },
message: "must match format \"email\""
}
]
*/
}
// 型の種類
const stringSchema = { type: "string" };
const numberSchema = { type: "number" };
const booleanSchema = { type: "boolean" };
const arraySchema = { type: "array", items: { type: "string" } };
const objectSchema = { type: "object" };
// バリデーション実行
stringSchema.parse = ajv.compile(stringSchema);
console.log(stringSchema.parse("Hello")); // true
console.log(stringSchema.parse(123)); // false
高度なバリデーションルールとカスタムバリデーター
// 数値の範囲指定
const ageSchema = {
type: "number",
minimum: 0,
maximum: 150,
multipleOf: 1 // 整数のみ
};
// 文字列の詳細バリデーション
const passwordSchema = {
type: "string",
minLength: 8,
maxLength: 50,
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)" // 大文字、小文字、数字を含む
};
// 配列とネストオブジェクト
const productSchema = {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
name: { type: "string", minLength: 1 },
price: { type: "number", minimum: 0 },
categories: {
type: "array",
items: { type: "string" },
minItems: 1
},
metadata: {
type: "object",
additionalProperties: { type: "string" }
}
},
required: ["id", "name", "price"],
additionalProperties: false
};
// 条件付きバリデーション(if/then/else)
const userSchema = {
type: "object",
properties: {
role: { type: "string", enum: ["admin", "user", "guest"] },
permissions: { type: "array", items: { type: "string" } },
department: { type: "string" }
},
if: { properties: { role: { const: "admin" } } },
then: { required: ["permissions"] },
else: {
if: { properties: { role: { const: "user" } } },
then: { required: ["department"] }
}
};
// カスタムキーワードの定義
ajv.addKeyword({
keyword: "even",
type: "number",
schemaType: "boolean",
compile: function(schemaVal) {
return function validate(data) {
if (schemaVal) {
return data % 2 === 0;
}
return true;
};
}
});
// カスタムキーワードの使用
const evenNumberSchema = {
type: "number",
even: true
};
const validateEven = ajv.compile(evenNumberSchema);
console.log(validateEven(4)); // true
console.log(validateEven(3)); // false
フレームワーク統合(Express、Fastify、NestJS等)
// Express.jsでのミドルウェア例
const express = require('express');
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const ajv = new Ajv();
addFormats(ajv); // 標準フォーマット(email、uri等)を追加
const app = express();
app.use(express.json());
// バリデーションミドルウェア
const validateRequest = (schema) => {
const validate = ajv.compile(schema);
return (req, res, next) => {
const valid = validate(req.body);
if (!valid) {
return res.status(400).json({
error: "バリデーションエラー",
details: validate.errors
});
}
next();
};
};
// APIエンドポイントでの使用
const createUserSchema = {
type: "object",
properties: {
name: { type: "string", minLength: 1, maxLength: 100 },
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 18, maximum: 120 }
},
required: ["name", "email", "age"],
additionalProperties: false
};
app.post('/users', validateRequest(createUserSchema), (req, res) => {
// req.bodyは型安全にアクセス可能
res.json({ message: "ユーザー作成成功", user: req.body });
});
// Fastifyでの統合例
const fastify = require('fastify')({ logger: true });
const userRouteSchema = {
body: {
type: 'object',
required: ['name', 'email'],
properties: {
name: { type: 'string' },
email: { type: 'string', format: 'email' }
}
},
response: {
200: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
};
fastify.post('/users', { schema: userRouteSchema }, async (request, reply) => {
// requestは自動的にバリデーションされる
return { id: 'generated-id', ...request.body };
});
// NestJSでのValidationPipeとの併用
import { Injectable, PipeTransform, BadRequestException } from '@nestjs/common';
import Ajv from 'ajv';
@Injectable()
export class AjvValidationPipe implements PipeTransform {
private ajv = new Ajv();
constructor(private schema: any) {}
transform(value: any) {
const validate = this.ajv.compile(this.schema);
const isValid = validate(value);
if (!isValid) {
throw new BadRequestException({
message: 'バリデーションエラー',
errors: validate.errors
});
}
return value;
}
}
エラーハンドリングとカスタムエラーメッセージ
// 詳細なエラーハンドリング
const schema = {
type: "object",
properties: {
name: { type: "string", minLength: 2 },
age: { type: "number", minimum: 0 }
},
required: ["name", "age"]
};
const validate = ajv.compile(schema);
const result = validate({ name: "A", age: -1 });
if (!result) {
console.log("エラー詳細:", validate.errors);
/*
[
{
instancePath: "/name",
schemaPath: "#/properties/name/minLength",
keyword: "minLength",
params: { limit: 2 },
message: "must NOT have fewer than 2 characters"
},
{
instancePath: "/age",
schemaPath: "#/properties/age/minimum",
keyword: "minimum",
params: { comparison: ">=", limit: 0 },
message: "must be >= 0"
}
]
*/
}
// カスタムエラーメッセージ
const schemaWithCustomMessages = {
type: "object",
properties: {
email: {
type: "string",
format: "email",
errorMessage: "有効なメールアドレスを入力してください"
},
password: {
type: "string",
minLength: 8,
errorMessage: {
minLength: "パスワードは8文字以上である必要があります"
}
}
},
required: ["email", "password"],
errorMessage: {
required: {
email: "メールアドレスは必須です",
password: "パスワードは必須です"
}
}
};
// エラーメッセージプラグインの使用
const ajvErrors = require('ajv-errors');
ajvErrors(ajv);
// エラー情報の整理
function formatErrors(errors) {
const formattedErrors = {};
errors.forEach(error => {
const field = error.instancePath.slice(1) || error.params?.missingProperty;
if (field) {
formattedErrors[field] = error.message;
}
});
return formattedErrors;
}
// 使用例
const userData = { email: "invalid-email", password: "123" };
const isValid = validate(userData);
if (!isValid) {
const fieldErrors = formatErrors(validate.errors);
console.log(fieldErrors);
// { email: "有効なメールアドレスを入力してください", password: "パスワードは8文字以上である必要があります" }
}
TypeScript統合と型安全性
import Ajv, { JSONSchemaType } from 'ajv';
const ajv = new Ajv();
// TypeScript インターフェースの定義
interface User {
id: string;
name: string;
email: string;
age: number;
active?: boolean;
}
// TypeScript型に対応したJSON Schema
const userSchema: JSONSchemaType<User> = {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
email: { type: "string", format: "email" },
age: { type: "number", minimum: 0 },
active: { type: "boolean", nullable: true }
},
required: ["id", "name", "email", "age"],
additionalProperties: false
};
// 型安全なバリデーション関数
const validateUser = ajv.compile(userSchema);
// 型ガード関数として使用
function isValidUser(data: unknown): data is User {
return validateUser(data);
}
// 使用例
const userData: unknown = {
id: "12345",
name: "田中太郎",
email: "[email protected]",
age: 30
};
if (isValidUser(userData)) {
// ここでuserDataはUser型として扱われる
console.log(`ユーザー名: ${userData.name}`);
console.log(`年齢: ${userData.age}`);
} else {
console.log("無効なユーザーデータ:", validateUser.errors);
}
// 複雑な型の例
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
timestamp: string;
}
const createApiResponseSchema = <T>(dataSchema: JSONSchemaType<T>): JSONSchemaType<ApiResponse<T>> => ({
type: "object",
properties: {
success: { type: "boolean" },
data: { ...dataSchema, nullable: true },
error: { type: "string", nullable: true },
timestamp: { type: "string", format: "date-time" }
},
required: ["success", "timestamp"],
additionalProperties: false
});
// ジェネリック型での使用
const userApiResponseSchema = createApiResponseSchema(userSchema);
const validateUserApiResponse = ajv.compile(userApiResponseSchema);
// 外部APIレスポンスの検証
async function fetchUser(id: string): Promise<ApiResponse<User> | null> {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
if (validateUserApiResponse(data)) {
return data; // 型安全にApiResponse<User>として返す
}
console.error("APIレスポンスのバリデーションエラー:", validateUserApiResponse.errors);
return null;
}
パフォーマンス最適化とベンチマーク
// スキーマのプリコンパイル(推奨)
const schemas = {
user: ajv.compile({
type: "object",
properties: {
name: { type: "string" },
email: { type: "string", format: "email" }
},
required: ["name", "email"]
}),
product: ajv.compile({
type: "object",
properties: {
id: { type: "string" },
price: { type: "number", minimum: 0 }
},
required: ["id", "price"]
})
};
// 大量データの高速バリデーション
function validateBulkData(items, validator) {
const startTime = performance.now();
const results = items.map(item => validator(item));
const endTime = performance.now();
console.log(`${items.length}件のバリデーション時間: ${endTime - startTime}ms`);
return results;
}
// メモリ効率的なバリデーション
const ajvFast = new Ajv({
code: { optimize: true },
allErrors: false, // 最初のエラーで停止(高速化)
verbose: false, // 詳細情報を削減
strict: false // 厳密モードを無効(高速化)
});
// ストリームデータのバリデーション
const stream = require('stream');
class ValidationStream extends stream.Transform {
constructor(schema) {
super({ objectMode: true });
this.validate = ajv.compile(schema);
}
_transform(chunk, encoding, callback) {
if (this.validate(chunk)) {
this.push(chunk);
} else {
this.emit('error', new Error(`バリデーションエラー: ${JSON.stringify(this.validate.errors)}`));
}
callback();
}
}
// 使用例
const validationStream = new ValidationStream(userSchema);
validationStream.on('error', (error) => {
console.error('ストリームバリデーションエラー:', error.message);
});