0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今更だけどJsonSchemaおさらい

Posted at

注意:この記事は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. 検証の層を分ける

    • 第1層:JSON Schemaによる構造検証(高速・同期的)
    • 第2層:ビジネスロジック検証(必要に応じて非同期)
  2. 拡張フィールドの使用ガイドライン

    • JSON Schema単体: $commentを使用(公式仕様)
    • OpenAPI環境: x-プレフィックスを使用(OpenAPI公式)
    • 独自システム: 別ファイルでメタデータ管理
    • 重要: x-フィールドは標準のJSON Schemaバリデーターでは無視される
  3. エラーメッセージの管理

    • JSON Schema:構造的なエラー
    • システム側:ビジネスロジックのエラー
    • 両者を区別して管理
  4. ドキュメント化

    • カスタムバリデーションは必ずドキュメント化
    • 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-1101

exclusiveMinimum / exclusiveMaximum - 範囲指定(境界値を除く)

{
  "type": "number",
  "exclusiveMinimum": 0,   // 0より大きい
  "exclusiveMaximum": 100  // 100未満
}

500100

multipleOf - 倍数制限

{
  "type": "number",
  "multipleOf": 0.5  // 0.5の倍数のみ
}

1.52.01.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)
}

5315 (両方の条件を満たすため)

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" }
  ]
}

参照リンク

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?