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は最速のJSON Schemaバリデーターライブラリです。JSON Schema draft-04/06/07/2019-09/2020-12とJSON Type Definition (RFC8927)をサポートし、高いパフォーマンスと豊富な機能を提供します。JSONスキーマ標準に準拠した堅牢なバリデーションシステムを構築できるため、企業向けアプリケーションやAPIサーバーで広く採用されています。
特徴
- 高速パフォーマンス: 業界最高速のJSON Schemaバリデーター
- 標準準拠: JSON Schema最新仕様完全対応
- TypeScript統合: 優れた型安全性とIntelliSense支援
- 非同期バリデーション: データベース検索やAPI呼び出しを含む検証
- カスタムキーワード: 独自バリデーションルールの定義
- カスタムフォーマット: 特殊な文字列フォーマットの検証
- エラーレポート: 詳細なエラー情報と位置特定
- コード生成: バリデーション関数の事前コンパイル
- JTDサポート: JSON Type Definition仕様対応
- 軽量: 最小限の依存関係とコンパクトなサイズ
インストール
npm install ajv
# または
yarn add ajv
# または
pnpm add ajv
# または
bun add ajv
# TypeScript用の型定義(既に含まれています)
# 追加のフォーマット検証が必要な場合
npm install ajv-formats
使用例
基本的な使用方法
import Ajv from 'ajv';
// AJVインスタンスの作成
const ajv = new Ajv();
// JSON Schemaの定義
const schema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number", minimum: 0, maximum: 150 },
email: { type: "string", format: "email" },
isActive: { type: "boolean", default: true }
},
required: ["name", "age", "email"],
additionalProperties: false
};
// スキーマのコンパイル
const validate = ajv.compile(schema);
// データの検証
const userData = {
name: "山田太郎",
age: 30,
email: "[email protected]"
};
if (validate(userData)) {
console.log("検証成功:", userData);
} else {
console.log("検証エラー:", validate.errors);
}
// TypeScriptでの型安全な検証
interface User {
name: string;
age: number;
email: string;
isActive?: boolean;
}
const validateUser = ajv.compile<User>(schema);
function processUser(data: unknown): User | null {
if (validateUser(data)) {
// dataはUser型として型推論される
return data;
}
console.error("User validation failed:", validateUser.errors);
return null;
}
JSON Schemaの高度な機能
// 複雑なスキーマの定義
const productSchema = {
type: "object",
properties: {
id: { type: "string", pattern: "^[A-Z]{2}[0-9]{4}$" },
name: { type: "string", minLength: 1, maxLength: 100 },
price: { type: "number", minimum: 0, multipleOf: 0.01 },
category: { enum: ["electronics", "clothing", "books", "home"] },
tags: {
type: "array",
items: { type: "string" },
minItems: 1,
maxItems: 10,
uniqueItems: true
},
variants: {
type: "array",
items: {
type: "object",
properties: {
size: { type: "string" },
color: { type: "string" },
stock: { type: "integer", minimum: 0 }
},
required: ["size", "color", "stock"]
}
},
metadata: {
type: "object",
patternProperties: {
"^[a-z_]+$": { type: "string" }
},
additionalProperties: false
}
},
required: ["id", "name", "price", "category"],
additionalProperties: false
};
// 条件付きスキーマ
const orderSchema = {
type: "object",
properties: {
type: { enum: ["physical", "digital"] },
amount: { type: "number", minimum: 0 },
shippingAddress: { type: "string" },
downloadLink: { type: "string", format: "uri" }
},
required: ["type", "amount"],
if: { properties: { type: { const: "physical" } } },
then: { required: ["shippingAddress"] },
else: { required: ["downloadLink"] }
};
// スキーマの参照と再利用
const addressSchema = {
$id: "https://example.com/address.json",
type: "object",
properties: {
street: { type: "string" },
city: { type: "string" },
zipCode: { type: "string", pattern: "^[0-9]{5}(-[0-9]{4})?$" }
},
required: ["street", "city", "zipCode"]
};
const customerSchema = {
type: "object",
properties: {
name: { type: "string" },
billingAddress: { $ref: "https://example.com/address.json" },
shippingAddress: { $ref: "https://example.com/address.json" }
},
required: ["name", "billingAddress"]
};
// スキーマの登録と使用
ajv.addSchema(addressSchema);
const validateCustomer = ajv.compile(customerSchema);
カスタムキーワードとフォーマット
import addFormats from 'ajv-formats';
// フォーマット検証の追加
const ajv = new Ajv();
addFormats(ajv);
// カスタムキーワードの定義
ajv.addKeyword({
keyword: "range",
type: "number",
schemaType: "array",
compile(schemaValue: [number, number]) {
const [min, max] = schemaValue;
return function validate(data: number): boolean {
return data >= min && data <= max;
};
}
});
// カスタムフォーマットの定義
ajv.addFormat("japanese-phone", {
type: "string",
validate: function(data: string): boolean {
// 日本の電話番号形式をチェック
return /^0\d{1,4}-\d{1,4}-\d{4}$/.test(data);
}
});
// カスタムキーワードとフォーマットの使用
const schema = {
type: "object",
properties: {
score: { type: "number", range: [0, 100] },
phone: { type: "string", format: "japanese-phone" },
email: { type: "string", format: "email" },
date: { type: "string", format: "date" }
}
};
// より高度なカスタムキーワード(非同期対応)
ajv.addKeyword({
keyword: "uniqueEmail",
type: "string",
format: "email",
async: true,
compile() {
return async function validate(email: string): Promise<boolean> {
// データベースでメールアドレスの重複をチェック
// const exists = await checkEmailExists(email);
// return !exists;
// 模擬的な非同期チェック
return new Promise((resolve) => {
setTimeout(() => {
resolve(!email.includes("taken"));
}, 100);
});
};
}
});
非同期バリデーション
// 非同期バリデーションスキーマ
const asyncSchema = {
type: "object",
properties: {
username: {
type: "string",
minLength: 3,
uniqueUsername: true
},
email: {
type: "string",
format: "email",
uniqueEmail: true
}
},
required: ["username", "email"]
};
// 非同期バリデーション関数
ajv.addKeyword({
keyword: "uniqueUsername",
type: "string",
async: true,
compile() {
return async function validate(username: string): Promise<boolean> {
// データベースでユーザー名の重複をチェック
console.log(`Checking username: ${username}`);
await new Promise(resolve => setTimeout(resolve, 200));
return !["admin", "root", "system"].includes(username.toLowerCase());
};
}
});
// 非同期バリデーションの実行
const validateAsync = ajv.compile(asyncSchema);
async function validateUserData(data: unknown) {
try {
const valid = await validateAsync(data);
if (valid) {
console.log("非同期検証成功:", data);
return data;
} else {
console.error("非同期検証エラー:", validateAsync.errors);
return null;
}
} catch (error) {
console.error("非同期検証で例外発生:", error);
return null;
}
}
// 使用例
validateUserData({
username: "newuser",
email: "[email protected]"
});
エラーハンドリングとカスタマイズ
// 詳細なエラー情報の取得
const ajv = new Ajv({
allErrors: true, // すべてのエラーを収集
verbose: true, // 詳細な情報を含める
$data: true, // $dataリファレンスを有効化
removeAdditional: true // 追加プロパティを自動削除
});
const schema = {
type: "object",
properties: {
name: { type: "string", minLength: 2 },
age: { type: "number", minimum: 0 },
email: { type: "string", format: "email" }
},
required: ["name", "age"]
};
const validate = ajv.compile(schema);
function validateWithDetailedErrors(data: unknown) {
const valid = validate(data);
if (!valid && validate.errors) {
const errorMessages = validate.errors.map(error => {
const { instancePath, keyword, message, params } = error;
return {
field: instancePath || "root",
rule: keyword,
message: message,
rejectedValue: error.data,
params: params
};
});
console.error("バリデーションエラー詳細:", errorMessages);
return { success: false, errors: errorMessages };
}
return { success: true, data };
}
// カスタムエラーメッセージ
const customErrorSchema = {
type: "object",
properties: {
username: {
type: "string",
minLength: 3,
errorMessage: {
type: "ユーザー名は文字列である必要があります",
minLength: "ユーザー名は3文字以上で入力してください"
}
},
password: {
type: "string",
minLength: 8,
pattern: "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$",
errorMessage: {
minLength: "パスワードは8文字以上必要です",
pattern: "パスワードには大文字、小文字、数字を含める必要があります"
}
}
},
required: ["username", "password"],
errorMessage: {
required: {
username: "ユーザー名は必須項目です",
password: "パスワードは必須項目です"
}
}
};
// ajv-errorsプラグインを使用
import ajvErrors from 'ajv-errors';
ajvErrors(ajv);
パフォーマンス最適化
// コード生成による最適化
const ajv = new Ajv({
code: {
optimize: true, // コード最適化を有効化
regExp: false // 正規表現の最適化を無効化(互換性のため)
}
});
// スタンドアロン関数の生成
import standaloneCode from 'ajv/dist/standalone';
const schema = {
type: "object",
properties: {
id: { type: "number" },
name: { type: "string" }
},
required: ["id", "name"]
};
const validate = ajv.compile(schema);
const moduleCode = standaloneCode(ajv, validate);
// 生成されたコードをファイルに保存
// このコードは依存関係なしで実行可能
console.log(moduleCode);
// スキーマキャッシュの活用
const schemaCache = new Map();
function getCachedValidator(schemaId: string, schema: object) {
if (schemaCache.has(schemaId)) {
return schemaCache.get(schemaId);
}
const validator = ajv.compile(schema);
schemaCache.set(schemaId, validator);
return validator;
}
// バッチバリデーション
function validateBatch<T>(validator: any, dataArray: unknown[]): T[] {
const results: T[] = [];
for (const data of dataArray) {
if (validator(data)) {
results.push(data as T);
} else {
console.warn("Invalid data skipped:", validator.errors);
}
}
return results;
}
API統合とミドルウェア
// Express.jsミドルウェア
import express from 'express';
interface ValidationSchema {
body?: object;
query?: object;
params?: object;
}
function createValidator(schemas: ValidationSchema) {
const validators = {
body: schemas.body ? ajv.compile(schemas.body) : null,
query: schemas.query ? ajv.compile(schemas.query) : null,
params: schemas.params ? ajv.compile(schemas.params) : null
};
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
const errors: any[] = [];
// リクエストボディの検証
if (validators.body && !validators.body(req.body)) {
errors.push({
location: "body",
errors: validators.body.errors
});
}
// クエリパラメータの検証
if (validators.query && !validators.query(req.query)) {
errors.push({
location: "query",
errors: validators.query.errors
});
}
// パスパラメータの検証
if (validators.params && !validators.params(req.params)) {
errors.push({
location: "params",
errors: validators.params.errors
});
}
if (errors.length > 0) {
return res.status(400).json({
error: "バリデーションエラー",
details: errors
});
}
next();
};
}
// 使用例
const app = express();
app.use(express.json());
const userCreateSchema = {
body: {
type: "object",
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
age: { type: "number", minimum: 0 }
},
required: ["name", "email"],
additionalProperties: false
}
};
app.post('/users',
createValidator(userCreateSchema),
(req: express.Request, res: express.Response) => {
// req.bodyは検証済み
const userData = req.body;
console.log("新しいユーザー:", userData);
res.json({ success: true, user: userData });
}
);
// Fastifyプラグインとしての使用
import fastify from 'fastify';
const server = fastify();
server.addHook('preHandler', async (request, reply) => {
if (request.routerMethod === 'POST' && request.url === '/api/users') {
const userSchema = {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string", format: "email" }
},
required: ["name", "email"]
};
const validate = ajv.compile(userSchema);
if (!validate(request.body)) {
reply.code(400).send({
error: "Validation failed",
details: validate.errors
});
}
}
});
JSON Type Definition (JTD)の使用
import Ajv from 'ajv/dist/jtd';
// JTD専用のAJVインスタンス
const ajvJTD = new Ajv();
// JTDスキーマの定義
const userSchemaJTD = {
properties: {
name: { type: "string" },
age: { type: "uint16" },
email: { type: "string" }
},
optionalProperties: {
isActive: { type: "boolean" }
},
additionalProperties: false
};
// TypeScript型の生成
import { JTDSchemaType } from 'ajv/dist/jtd';
interface User {
name: string;
age: number;
email: string;
isActive?: boolean;
}
const schema: JTDSchemaType<User> = {
properties: {
name: { type: "string" },
age: { type: "uint16" },
email: { type: "string" }
},
optionalProperties: {
isActive: { type: "boolean" }
}
};
const validateJTD = ajvJTD.compile(schema);
// 型安全なバリデーション
function processUserJTD(data: unknown): User | null {
if (validateJTD(data)) {
// dataは自動的にUser型として推論される
return data;
}
return null;
}
比較・代替手段
類似ライブラリとの比較
- Zod: TypeScript-firstで優れたDX、but JSON Schema標準非対応
- Yup: 使いやすいAPI、but パフォーマンスが劣る
- Joi: 豊富な機能、but ブラウザでのサイズが大きい
- io-ts: 関数型アプローチ、but 学習コストが高い
- JSON Schema libraries: 他の実装よりもAJVが最速
AJVを選ぶべき場合
- JSON Schema標準準拠が必要
- 高いパフォーマンスが求められる
- 大量のデータ検証が必要
- 既存のJSON Schemaアセットを活用したい
- 非同期バリデーションが必要
- エンタープライズレベルの堅牢性が必要
学習リソース
まとめ
AJVは業界標準のJSON Schemaバリデーターとして、高いパフォーマンスと豊富な機能を提供します。特に、大規模なAPIサーバーやエンタープライズアプリケーションにおいて、データの整合性とパフォーマンスの両方が求められる場面で威力を発揮します。JSON Schema標準への準拠により、他のシステムとの相互運用性も確保でき、長期的なメンテナンス性にも優れています。TypeScriptとの統合により、型安全性を保ちながら実行時バリデーションを実現できる点も大きな魅力です。