注意:この記事はAIが作成しています
参照元の存在、参考元のリンクの信頼度、事実の歪曲がないかをAIによりセルフチェックしています
なぜJSON Schemaを使うのか? - 限界を理解した上でのメリット
JSON Schemaだけでは完全な検証ができないのに、なぜ使うのでしょうか?実は、その「限界」を理解した上で使うことに大きな価値があります。
JSON Schemaの本質的なメリット
1. 高速な第一段階フィルタリング
メリット:
- 明らかに不正なデータを高速に排除(ミリ秒単位)
- DBアクセスや外部API呼び出しの前に検証
- サーバーリソースの大幅な節約
// ❌ JSON Schemaなしの場合
async function validateOrder(data) {
// いきなりDB検証(重い処理)
const userExists = await db.query('SELECT id FROM users WHERE id = ?', [data.userId]);
if (!userExists) return {error: 'ユーザーが存在しません'};
// ここで初めて型エラーに気づく可能性
if (typeof data.quantity !== 'number') return {error: '数量は数値である必要があります'};
// ...
}
// ✅ JSON Schemaありの場合
async function validateOrder(data) {
// 構造検証(軽い処理)で70%の不正データを排除
const structureValid = ajv.validate(schema, data);
if (!structureValid) return {errors: ajv.errors}; // 即座に返却
// 構造的に正しいデータのみDB検証
const userExists = await db.query('SELECT id FROM users WHERE id = ?', [data.userId]);
// ...
}
2. 言語・プラットフォーム非依存の仕様書
# 同じスキーマを複数の環境で使用
environments:
frontend:
- TypeScript型定義の自動生成
- フォームバリデーション
backend:
- Node.js (Ajv)
- Python (jsonschema)
- Go (gojsonschema)
mobile:
- Swift (JSONSchema)
- Kotlin (json-schema-validator)
documentation:
- OpenAPI/Swagger
- 自動ドキュメント生成
具体例:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://api.example.com/schemas/user.json",
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 }
}
}
このスキーマから:
// TypeScript型を自動生成
interface User {
email: string;
age: number;
}
// Reactフォームバリデーションも自動生成
const userValidation = {
email: yup.string().email(),
age: yup.number().min(0).max(150)
};
3. 開発効率の劇的な向上
Before(手動実装):
function validateUser(data) {
const errors = [];
if (!data.email) {
errors.push('メールアドレスは必須です');
} else if (typeof data.email !== 'string') {
errors.push('メールアドレスは文字列である必要があります');
} else if (!data.email.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/)) {
errors.push('有効なメールアドレスを入力してください');
}
if (data.age !== undefined) {
if (typeof data.age !== 'number') {
errors.push('年齢は数値である必要があります');
} else if (data.age < 0 || data.age > 150) {
errors.push('年齢は0〜150の範囲で入力してください');
}
}
return errors.length > 0 ? {valid: false, errors} : {valid: true};
}
After(JSON Schema):
const schema = {
type: "object",
properties: {
email: { type: "string", format: "email" },
age: { type: "integer", minimum: 0, maximum: 150 }
},
required: ["email"]
};
const validate = ajv.compile(schema);
// 完了!エラーメッセージも自動生成
4. API契約としての価値
5. 段階的な検証による最適化
// 実践的な検証フロー
class DataValidator {
constructor(schema) {
this.schemaValidator = ajv.compile(schema);
this.businessRules = new BusinessRuleValidator();
}
async validate(data) {
// ステージ1: 構造検証(1ms以下)
if (!this.schemaValidator(data)) {
return {
valid: false,
stage: 'structure',
errors: this.schemaValidator.errors,
cost: 'low' // リソース消費: 低
};
}
// ステージ2: 同期的ビジネスルール(10ms以下)
const syncErrors = this.businessRules.validateSync(data);
if (syncErrors.length > 0) {
return {
valid: false,
stage: 'business-sync',
errors: syncErrors,
cost: 'medium' // リソース消費: 中
};
}
// ステージ3: 非同期検証(100ms以上)
const asyncErrors = await this.businessRules.validateAsync(data);
if (asyncErrors.length > 0) {
return {
valid: false,
stage: 'business-async',
errors: asyncErrors,
cost: 'high' // リソース消費: 高
};
}
return { valid: true };
}
}
コスト削減の具体例
// 実際のメトリクス例
const validationMetrics = {
withoutJsonSchema: {
totalRequests: 10000,
dbQueries: 10000, // すべてのリクエストでDB確認
avgResponseTime: '150ms',
dbCost: '$2.50', // クラウドDBのクエリコスト
},
withJsonSchema: {
totalRequests: 10000,
structureInvalid: 7000, // JSON Schemaで即座に却下
dbQueries: 3000, // 構造的に有効なものだけDB確認
avgResponseTime: '45ms',
dbCost: '$0.75', // 70%のコスト削減
savings: '70% reduction in DB costs'
}
};
JSON Schemaを使うべきケース・使わないべきケース
✅ 使うべきケース
- 公開API: 明確な契約が必要
- マイクロサービス: サービス間の通信仕様
- 大規模チーム: 共通認識の形成
- 多言語環境: 言語非依存の仕様が必要
- 高トラフィック: 第一段階フィルタリングが効果的
❌ 使わないべきケース
- 小規模な内部ツール: オーバーエンジニアリング
- 頻繁に変更される仕様: メンテナンスコスト高
- 複雑なビジネスロジック中心: JSON Schemaでは表現不可
まとめ:JSON Schemaの正しい位置づけ
JSON Schemaは「完全な検証ソリューション」ではなく、「効率的な多層防御の第一層」として使うべきツールです。
結論: JSON Schemaは万能ではありませんが、適切に使えば開発効率とシステムパフォーマンスを大幅に向上させる強力なツールです。
JSON Schemaは構造的な検証は得意ですが、ビジネスロジックや動的な検証は苦手です。以下のような検証はシステム側で実装する必要があります。
表現できない検証の例
1. 動的な日時の検証
{
"type": "object",
"properties": {
"expiryDate": {
"type": "string",
"format": "date-time",
"description": "⚠️ システム側で「現在時刻より未来」を検証"
},
"startDate": {
"type": "string",
"format": "date-time"
},
"endDate": {
"type": "string",
"format": "date-time",
"description": "⚠️ システム側で「startDateより後」を検証"
}
}
}
2. データベースとの整合性チェック
{
"type": "object",
"properties": {
"userId": {
"type": "string",
"format": "uuid",
"description": "⚠️ システム側で「存在するユーザーID」を検証"
},
"uniqueEmail": {
"type": "string",
"format": "email",
"description": "⚠️ システム側で「重複していないメール」を検証"
}
}
}
3. 複雑な計算や集計の検証
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"price": { "type": "number" },
"quantity": { "type": "integer" }
}
}
},
"total": {
"type": "number",
"description": "⚠️ システム側で「items の price * quantity の合計と一致」を検証"
},
"discount": {
"type": "number",
"description": "⚠️ システム側で「合計金額に応じた割引率の妥当性」を検証"
}
}
}
4. 外部APIやサービスとの連携
{
"type": "object",
"properties": {
"zipCode": {
"type": "string",
"pattern": "^\\d{3}-\\d{4}$",
"description": "⚠️ システム側で「実在する郵便番号」を検証"
},
"creditCardNumber": {
"type": "string",
"pattern": "^\\d{16}$",
"description": "⚠️ システム側で「有効なクレジットカード」を検証"
}
}
}
システムへの検証要件の伝達方法
方法1: カスタム拡張フィールドの使用(非標準)
⚠️ 重要: x- プレフィックスを持つフィールドはJSON Schemaの公式仕様ではありません。これらは拡張フィールドとして、特定のシステムやツール間での情報伝達に使用される慣習的な方法です。
JSON Schemaバリデーターは通常これらのフィールドを無視しますが、アプリケーション側で独自に処理することができます:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"expiryDate": {
"type": "string",
"format": "date-time",
"x-validation": { // ⚠️ 非標準の拡張フィールド
"type": "future-date",
"message": "有効期限は未来の日付である必要があります"
}
},
"userId": {
"type": "string",
"format": "uuid",
"x-custom-rules": { // ⚠️ 非標準の拡張フィールド
"exists-in-database": true,
"table": "users",
"column": "id"
}
}
}
}
なぜ x- プレフィックスを使うのか?
- OpenAPIなどの仕様で拡張フィールドの慣習として確立
- 標準のJSON Schemaキーワードと区別できる
- バリデーターが予期せぬエラーを起こさない
- 将来的に公式仕様と競合しない
方法2: $commentを使った検証ルールの記述(公式仕様)
JSON Schemaの公式仕様である$commentを使用する方法:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"expiryDate": {
"type": "string",
"format": "date-time",
"$comment": "VALIDATION_RULE: must be future date from current time"
},
"email": {
"type": "string",
"format": "email",
"$comment": "VALIDATION_RULE: must be unique in users table"
}
}
}
方法3: 別ファイルでのメタデータ管理
JSON Schema本体とは別に、ビジネスルールを定義:
schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/order.json",
"type": "object",
"properties": {
"startDate": { "type": "string", "format": "date" },
"endDate": { "type": "string", "format": "date" }
}
}
schema-rules.json
{
"schemaId": "https://example.com/schemas/order.json",
"businessRules": [
{
"field": "endDate",
"rule": "must-be-after",
"compareField": "startDate"
}
]
}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"validationRules": {
"businessRules": [
{
"field": "endDate",
"rule": "must-be-after",
"compareField": "startDate",
"errorMessage": "終了日は開始日より後である必要があります"
},
{
"field": "total",
"rule": "calculate-sum",
"formula": "sum(items[].price * items[].quantity)",
"errorMessage": "合計金額が正しくありません"
}
],
"asyncValidations": [
{
"field": "email",
"endpoint": "/api/validate/email-unique",
"method": "POST",
"errorMessage": "このメールアドレスは既に登録されています"
}
]
}
},
"type": "object",
"properties": {
"startDate": { "type": "string", "format": "date" },
"endDate": { "type": "string", "format": "date" },
"email": { "type": "string", "format": "email" }
}
}
方法5: OpenAPI仕様での拡張(OpenAPIの公式仕様)
OpenAPI(Swagger)では、x-プレフィックスによる拡張が公式にサポートされています:
components:
schemas:
Order:
type: object
properties:
deliveryDate:
type: string
format: date-time
x-business-rule: |
- 現在時刻から24時間以上先であること
- 営業日であること
- 配送可能エリアの場合のみ指定可能
couponCode:
type: string
x-validation:
type: external
endpoint: /api/coupons/validate
params:
- code
- userId
- orderAmount
実装例:カスタムバリデーション(JavaScript/Ajv)
const Ajv = require("ajv");
const ajv = new Ajv();
// カスタムキーワードの登録
ajv.addKeyword({
keyword: "futureDate",
compile: function(schema) {
return function(data) {
if (!data) return true;
return new Date(data) > new Date();
};
}
});
ajv.addKeyword({
keyword: "afterField",
compile: function(fieldName) {
return function(data, dataPath, parentData) {
if (!parentData[fieldName] || !data) return true;
return new Date(data) > new Date(parentData[fieldName]);
};
}
});
// スキーマ定義
const schema = {
type: "object",
properties: {
startDate: {
type: "string",
format: "date-time"
},
endDate: {
type: "string",
format: "date-time",
afterField: "startDate" // カスタムキーワード使用
},
expiryDate: {
type: "string",
format: "date-time",
futureDate: true // カスタムキーワード使用
}
}
};
// 非同期バリデーション
ajv.addKeyword({
keyword: "uniqueEmail",
async: true,
compile: function() {
return async function(data) {
const response = await fetch(`/api/check-email?email=${data}`);
const result = await response.json();
return result.isUnique;
};
}
});
バリデーションの分離戦略
ベストプラクティス
-
検証の層を分ける
- 第1層:JSON Schemaによる構造検証(高速・同期的)
- 第2層:ビジネスロジック検証(必要に応じて非同期)
-
拡張フィールドの使用ガイドライン
-
JSON Schema単体:
$commentを使用(公式仕様) -
OpenAPI環境:
x-プレフィックスを使用(OpenAPI公式) - 独自システム: 別ファイルでメタデータ管理
-
重要:
x-フィールドは標準のJSON Schemaバリデーターでは無視される
-
JSON Schema単体:
-
エラーメッセージの管理
- JSON Schema:構造的なエラー
- システム側:ビジネスロジックのエラー
- 両者を区別して管理
-
ドキュメント化
- カスタムバリデーションは必ずドキュメント化
-
descriptionや$commentフィールドに検証内容を明記
実践例:ECサイトの注文スキーマ
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "注文データ",
"type": "object",
"properties": {
"customerId": {
"type": "string",
"format": "uuid",
"description": "顧客ID",
"x-validation": {
"type": "exists",
"table": "customers",
"message": "顧客が存在しません"
}
},
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"productId": {
"type": "string",
"format": "uuid",
"x-validation": {
"type": "exists",
"table": "products",
"message": "商品が存在しません"
}
},
"quantity": {
"type": "integer",
"minimum": 1,
"x-validation": {
"type": "stock-check",
"message": "在庫が不足しています"
}
},
"price": {
"type": "number",
"minimum": 0,
"x-validation": {
"type": "current-price",
"message": "価格が最新ではありません"
}
}
},
"required": ["productId", "quantity", "price"]
}
},
"couponCode": {
"type": "string",
"x-validation": {
"type": "coupon-valid",
"checks": ["exists", "not-expired", "usage-limit", "minimum-amount"],
"message": "クーポンが無効です"
}
},
"deliveryDate": {
"type": "string",
"format": "date",
"x-validation": {
"type": "delivery-available",
"checks": ["future-date", "business-day", "delivery-area"],
"message": "配送日として指定できません"
}
},
"total": {
"type": "number",
"minimum": 0,
"x-validation": {
"type": "calculate",
"formula": "sum(items) - discount + tax + shipping",
"message": "合計金額が正しくありません"
}
}
},
"required": ["customerId", "items", "deliveryDate", "total"],
"x-validation-order": [
"structure",
"existence",
"business-rules",
"calculations"
]
}
JSON Schema 定義項目リファレンス - 各キーワードの機能と使い方
スキーマの構造化と再利用
$ref - スキーマの参照(最重要)
JSON Schemaで最も重要な機能の一つが$refによる参照です。これにより、スキーマの再利用と構造化が可能になります。
基本的な使い方
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"email": {
"type": "string",
"format": "email"
}
},
"type": "object",
"properties": {
"personalEmail": { "$ref": "#/$defs/email" },
"workEmail": { "$ref": "#/$defs/email" }
}
}
$defs - 定義の格納場所
再利用可能なスキーマ定義を格納する標準的な場所です。
{
"$defs": {
"positiveInteger": {
"type": "integer",
"minimum": 1
},
"phoneNumber": {
"type": "string",
"pattern": "^\\d{2,4}-\\d{2,4}-\\d{4}$"
}
}
}
参照パスの種類
1. 内部参照(同一ファイル内)
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zipCode": { "type": "string" }
},
"required": ["street", "city"]
}
},
"type": "object",
"properties": {
"homeAddress": { "$ref": "#/$defs/address" },
"workAddress": { "$ref": "#/$defs/address" },
"billingAddress": { "$ref": "#/$defs/address" }
}
}
2. 外部ファイル参照
{
"type": "object",
"properties": {
"user": { "$ref": "definitions/user.json" },
"product": { "$ref": "definitions/product.json" }
}
}
3. URL参照
{
"type": "object",
"properties": {
"address": {
"$ref": "https://example.com/schemas/address.json"
}
}
}
4. 特定部分への参照
{
"$defs": {
"person": {
"type": "object",
"properties": {
"name": { "type": "string" },
"contact": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"phone": { "type": "string" }
}
}
}
}
},
"type": "object",
"properties": {
"emailOnly": {
"$ref": "#/$defs/person/properties/contact/properties/email"
}
}
}
実践的な構造化パターン
パターン1: 共通定義の分離
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"id": {
"type": "string",
"format": "uuid"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"money": {
"type": "object",
"properties": {
"amount": { "type": "number", "minimum": 0 },
"currency": {
"type": "string",
"enum": ["JPY", "USD", "EUR"]
}
},
"required": ["amount", "currency"]
}
},
"type": "object",
"properties": {
"orderId": { "$ref": "#/$defs/id" },
"customerId": { "$ref": "#/$defs/id" },
"orderDate": { "$ref": "#/$defs/timestamp" },
"totalAmount": { "$ref": "#/$defs/money" },
"shippingFee": { "$ref": "#/$defs/money" }
}
}
パターン2: ドメインモデルの定義
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"User": {
"type": "object",
"properties": {
"id": { "$ref": "#/$defs/common/id" },
"name": { "type": "string" },
"email": { "$ref": "#/$defs/common/email" },
"profile": { "$ref": "#/$defs/Profile" }
}
},
"Profile": {
"type": "object",
"properties": {
"bio": { "type": "string", "maxLength": 500 },
"avatarUrl": { "type": "string", "format": "uri" },
"birthDate": { "type": "string", "format": "date" }
}
},
"common": {
"id": {
"type": "string",
"format": "uuid"
},
"email": {
"type": "string",
"format": "email"
}
}
},
"$ref": "#/$defs/User"
}
パターン3: APIレスポンスの構造化
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"Meta": {
"type": "object",
"properties": {
"page": { "type": "integer", "minimum": 1 },
"perPage": { "type": "integer", "minimum": 1 },
"total": { "type": "integer", "minimum": 0 },
"totalPages": { "type": "integer", "minimum": 0 }
}
},
"Error": {
"type": "object",
"properties": {
"code": { "type": "string" },
"message": { "type": "string" },
"details": { "type": "object" }
},
"required": ["code", "message"]
},
"SuccessResponse": {
"type": "object",
"properties": {
"success": { "const": true },
"data": { "type": "object" },
"meta": { "$ref": "#/$defs/Meta" }
},
"required": ["success", "data"]
},
"ErrorResponse": {
"type": "object",
"properties": {
"success": { "const": false },
"error": { "$ref": "#/$defs/Error" }
},
"required": ["success", "error"]
}
},
"oneOf": [
{ "$ref": "#/$defs/SuccessResponse" },
{ "$ref": "#/$defs/ErrorResponse" }
]
}
循環参照の扱い
{
"$defs": {
"Node": {
"type": "object",
"properties": {
"value": { "type": "string" },
"children": {
"type": "array",
"items": { "$ref": "#/$defs/Node" }
}
}
}
},
"$ref": "#/$defs/Node"
}
これで木構造のような再帰的なデータ構造を定義できます。
基本的な型定義
$schema - スキーマバージョン指定
使用するJSON Schemaのバージョンを明示します。
{
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
主なバージョン:
-
draft/2020-12/schema- 最新版(推奨) -
draft-07/schema- 広くサポートされている -
draft-04/schema- レガシーシステム用
type - データ型を指定
{
"type": "string" // 文字列のみ許可
}
使用可能な値:
-
"string"- 文字列 -
"number"- 数値(整数・小数) -
"integer"- 整数のみ -
"boolean"- true/false -
"array"- 配列 -
"object"- オブジェクト -
"null"- null値
複数の型を許可する場合:
{
"type": ["string", "number"] // 文字列または数値を許可
}
文字列用キーワード
minLength / maxLength - 文字数制限
{
"type": "string",
"minLength": 3, // 最小3文字
"maxLength": 10 // 最大10文字
}
✅ "hello" ❌ "hi" ❌ "verylongstring"
pattern - 正規表現パターン
{
"type": "string",
"pattern": "^[A-Z][0-9]{3}$" // 大文字1文字 + 数字3文字
}
✅ "A123" ❌ "a123" ❌ "A12"
format - 定義済みフォーマット
{
"type": "string",
"format": "email" // メールアドレス形式
}
利用可能なフォーマット:
-
"date"- 日付 (2024-03-15) -
"time"- 時刻 (14:30:00) -
"date-time"- 日時 (2024-03-15T14:30:00Z) -
"email"- メールアドレス -
"hostname"- ホスト名 -
"ipv4"- IPv4アドレス -
"ipv6"- IPv6アドレス -
"uri"- URI -
"uuid"- UUID -
"regex"- 正規表現
enum - 選択肢を限定
{
"type": "string",
"enum": ["small", "medium", "large"] // この3つのみ許可
}
✅ "medium" ❌ "xl"
const - 固定値
{
"const": "固定値" // この値のみ許可
}
✅ "固定値" ❌ "他の値"
数値用キーワード
minimum / maximum - 範囲指定
{
"type": "number",
"minimum": 0, // 0以上
"maximum": 100 // 100以下
}
✅ 50 ❌ -1 ❌ 101
exclusiveMinimum / exclusiveMaximum - 範囲指定(境界値を除く)
{
"type": "number",
"exclusiveMinimum": 0, // 0より大きい
"exclusiveMaximum": 100 // 100未満
}
✅ 50 ❌ 0 ❌ 100
multipleOf - 倍数制限
{
"type": "number",
"multipleOf": 0.5 // 0.5の倍数のみ
}
✅ 1.5 ✅ 2.0 ❌ 1.3
配列用キーワード
items - 配列要素の定義
{
"type": "array",
"items": {
"type": "string" // 全要素が文字列
}
}
✅ ["a", "b", "c"] ❌ ["a", 1, "c"]
prefixItems - 位置ごとの要素定義
{
"type": "array",
"prefixItems": [
{ "type": "string" }, // 1番目は文字列
{ "type": "number" }, // 2番目は数値
{ "type": "boolean" } // 3番目は真偽値
]
}
✅ ["text", 123, true] ❌ [123, "text", true]
minItems / maxItems - 要素数制限
{
"type": "array",
"minItems": 1, // 最小1要素
"maxItems": 5 // 最大5要素
}
✅ [1, 2, 3] ❌ [] ❌ [1,2,3,4,5,6]
uniqueItems - 重複禁止
{
"type": "array",
"uniqueItems": true // 重複する要素を禁止
}
✅ [1, 2, 3] ❌ [1, 2, 2]
contains - 特定要素の存在確認
{
"type": "array",
"contains": {
"type": "number",
"minimum": 10
} // 10以上の数値を最低1つ含む
}
✅ [1, 5, 15] ❌ [1, 5, 9]
オブジェクト用キーワード
properties - プロパティ定義
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
}
}
✅ {"name": "太郎", "age": 20}
✅ {"name": "太郎", "age": 20, "city": "東京"} // 追加プロパティもOK
required - 必須プロパティ
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
},
"required": ["name"] // nameは必須
}
✅ {"name": "太郎"} ❌ {"age": 20}
additionalProperties - 追加プロパティの制御
{
"type": "object",
"properties": {
"name": { "type": "string" }
},
"additionalProperties": false // 定義外のプロパティを禁止
}
✅ {"name": "太郎"} ❌ {"name": "太郎", "age": 20}
patternProperties - パターンマッチするプロパティ
{
"type": "object",
"patternProperties": {
"^item_": { // "item_"で始まるプロパティ
"type": "number"
}
}
}
✅ {"item_1": 100, "item_2": 200} ❌ {"item_1": "text"}
dependentProperties - 依存プロパティ
{
"type": "object",
"properties": {
"creditCard": { "type": "string" },
"cvv": { "type": "string" }
},
"dependentProperties": {
"creditCard": ["cvv"] // creditCardがある場合、cvvも必須
}
}
✅ {"creditCard": "1234", "cvv": "123"}
✅ {}
❌ {"creditCard": "1234"}
組み合わせキーワード
allOf - すべての条件を満たす
{
"allOf": [
{ "type": "string" },
{ "minLength": 5 }
] // 文字列かつ5文字以上
}
anyOf - いずれかの条件を満たす
{
"anyOf": [
{ "type": "string" },
{ "type": "number" }
] // 文字列または数値
}
oneOf - 1つだけ条件を満たす
{
"oneOf": [
{ "type": "number", "multipleOf": 5 },
{ "type": "number", "multipleOf": 3 }
] // 5の倍数または3の倍数(両方はNG)
}
✅ 5 ✅ 3 ❌ 15 (両方の条件を満たすため)
not - 条件を満たさない
{
"not": {
"type": "string"
} // 文字列以外
}
条件分岐キーワード
if / then / else - 条件付き検証
{
"type": "object",
"properties": {
"type": { "type": "string" },
"value": { "type": ["string", "number"] }
},
"if": {
"properties": { "type": { "const": "number" } }
},
"then": {
"properties": { "value": { "type": "number" } }
},
"else": {
"properties": { "value": { "type": "string" } }
}
}
メタデータキーワード
title / description - 説明情報
{
"title": "ユーザー情報",
"description": "ユーザーの基本情報を定義",
"type": "object"
}
default - デフォルト値
{
"type": "string",
"default": "未設定"
}
examples - 使用例
{
"type": "string",
"pattern": "^[A-Z]{3}[0-9]{4}$",
"examples": ["ABC1234", "XYZ9876"]
}
deprecated - 非推奨マーク
{
"type": "string",
"deprecated": true // このフィールドは非推奨
}
readOnly / writeOnly - 読み書き制限
{
"properties": {
"id": {
"type": "string",
"readOnly": true // 読み取り専用(レスポンスのみ)
},
"password": {
"type": "string",
"writeOnly": true // 書き込み専用(リクエストのみ)
}
}
}
スキーマ設計のベストプラクティス
完全なAPIスキーマの例
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {
"common": {
"id": {
"type": "string",
"format": "uuid",
"readOnly": true
},
"timestamp": {
"type": "string",
"format": "date-time",
"readOnly": true
},
"email": {
"type": "string",
"format": "email",
"maxLength": 255
}
},
"models": {
"User": {
"type": "object",
"properties": {
"id": { "$ref": "#/$defs/common/id" },
"email": { "$ref": "#/$defs/common/email" },
"username": {
"type": "string",
"minLength": 3,
"maxLength": 30,
"pattern": "^[a-zA-Z0-9_-]+$"
},
"createdAt": { "$ref": "#/$defs/common/timestamp" },
"updatedAt": { "$ref": "#/$defs/common/timestamp" }
},
"required": ["email", "username"]
}
},
"requests": {
"CreateUser": {
"type": "object",
"properties": {
"email": { "$ref": "#/$defs/common/email" },
"username": {
"$ref": "#/$defs/models/User/properties/username"
},
"password": {
"type": "string",
"minLength": 8,
"writeOnly": true
}
},
"required": ["email", "username", "password"],
"additionalProperties": false
}
},
"responses": {
"UserResponse": {
"allOf": [
{ "$ref": "#/$defs/models/User" },
{
"type": "object",
"properties": {
"id": { "$ref": "#/$defs/common/id" },
"createdAt": { "$ref": "#/$defs/common/timestamp" },
"updatedAt": { "$ref": "#/$defs/common/timestamp" }
},
"required": ["id", "createdAt", "updatedAt"]
}
]
}
}
},
"oneOf": [
{ "$ref": "#/$defs/requests/CreateUser" },
{ "$ref": "#/$defs/responses/UserResponse" }
]
}