AI開発

JSON Schemaの書き方|API検証の5ステップ

株式会社Atsumell|10分で読めます
JSON Schemaの書き方をAPI検証の5ステップで整理する記事のサムネイル

はじめに

APIの仕様書に「金額はnumber」「メールアドレスはstring」と書いても、実装時の判断は残る。0円を許すのか。項目を省略できるのか。申請種別によって必須項目が変わるのか。余分な項目を受け取ったら無視するのか。

人が読む表だけでは、フロントエンド、バックエンド、テスト、AIコード生成が別々の解釈をしやすい。

JSON Schemaは、JSONデータの構造と制約を機械可読にする仕様だ。ただし、型を付けるだけでは業務で使える契約にならない。この記事では、経費申請APIを題材に、JSON Schemaの書き方を5ステップで整理する。

JSON Schemaは「入力例」ではなく検証できる契約

JSON Schema Draft 2020-12のValidation仕様には、`type`、`required`、`minimum`、`pattern`など、JSONインスタンスを検証する語彙が定義されている。Core仕様は、スキーマの構造、参照、評価の仕組みを定めている。

まず、利用する版を`$schema`で明示する。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object"
}

版を明示しないと、ツールが異なるdraftとして解釈し、同じ記述でも挙動がずれる可能性がある。OpenAPI 3.1のSchema Objectは、JSON Schema Draft 2020-12を基礎にした方言を採用している。OpenAPIと単体のJSON Schemaを併用する場合も、どの方言を正とするかを最初に合わせたい。

API仕様書の書き方で整理した「5つの契約」のうち、JSON Schemaが主に担当するのは入出力の構造だ。認証、再試行、状態遷移などを全部押し込むものではない。機械検証できる条件と、人が判断する業務ルールを分けて設計する。

ステップ1:対象と必須条件を最小構成で書く

最初から全項目を盛り込まず、APIが受け取るオブジェクトの境界を固定する。経費申請の最小構成は次のように書ける。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/expense-claim.json",
  "title": "経費申請",
  "type": "object",
  "required": ["expenseDate", "amount", "category"],
  "properties": {
    "expenseDate": {
      "type": "string",
      "format": "date",
      "description": "実際に支出した日"
    },
    "amount": {
      "type": "integer",
      "minimum": 1,
      "description": "税込の日本円"
    },
    "category": {
      "type": "string",
      "enum": ["transport", "lodging", "other"]
    }
  },
  "additionalProperties": false
}

重要なのは、型より意味である。`amount`を`integer`にしても、通貨、税込・税抜、上限、返金時の扱いまでは伝わらない。機械検証する条件はキーワードで書き、背景は`description`や要求IDに残す。

`required`は「値が空でない」ではなく、プロパティが存在することを求める。`null`を許すなら型として明示する。空文字を禁止するなら`minLength`が必要だ。省略、`null`、空文字を同じ意味で扱わない。

また、`additionalProperties: false`は未定義項目を拒否する。入力ミスの早期発見に役立つ一方、将来の項目追加を既存クライアントが拒否する設計にもなり得る。要求側と応答側で方針を分け、互換性を判断する。

ステップ2:値の範囲と表記を制約する

次に、業務上受け入れられる値を制約へ変える。

業務条件主なキーワード例
1円以上`minimum``"minimum": 1`
文字数上限`maxLength``"maxLength": 200`
候補を限定`enum`経費区分
配列件数`minItems`、`maxItems`添付は1〜5件
重複禁止`uniqueItems`タグの重複を禁止
文字パターン`pattern`社内管理番号

ここで、見た目の形式と業務上の正しさを混同しない。`format: "date"`は日付形式を示せても、「未来日は不可」「締め済み期間は不可」という現在状態に依存する判断までは表現しにくい。利用するバリデーターが`format`を検証として扱うかも確認が必要だ。

静的な構造検証はJSON Schema、データベースや権限を参照する判断はアプリケーションの業務検証、と責任を分ける。どちらか一方で済ませると、同じルールを複数画面へ重複実装したり、スキーマだけ通る不正な申請を受け入れたりする。

データフロー図の書き方でデータの発生元と保管先を整理しておくと、どの時点で何を検証するか決めやすい。

ステップ3:条件分岐を業務ルールから組み立てる

実務の入力条件は一定ではない。たとえば、宿泊費なら宿泊先が必須、1万円以上なら領収書が必須、といった分岐がある。

Draft 2020-12では`if`、`then`、`else`で条件を表現できる。

{
  "if": {
    "properties": {
      "category": { "const": "lodging" }
    },
    "required": ["category"]
  },
  "then": {
    "required": ["hotelName"]
  }
}

条件を増やす場合は`allOf`で規則を並べられる。ただし、複雑な分岐を一つの巨大なスキーマへ詰め込むと、レビューもエラー表示も難しくなる。

分岐を書く前に、デシジョンテーブルで条件と結果を整理する。デシジョンテーブルの書き方で、宿泊費、金額、領収書、事前申請の組み合わせを洗い出し、そのうち入力だけで判定できる規則をJSON Schemaへ移す。

目安は明快だ。

  • 入力JSONだけで合否が決まる:JSON Schema
  • 利用者の役職や権限が必要:認可処理
  • 締め状態や残予算が必要:業務サービス
  • 人の例外承認が必要:ワークフロー

この境界を要求IDで結ぶと、AIに実装を依頼するときも「スキーマへ書く条件」と「サービス層へ書く条件」を分けられる。

ステップ4:部品を再利用し、変更範囲を追えるようにする

住所、金額、添付ファイルなどをAPIごとにコピーすると、修正漏れが起きる。`$defs`と`$ref`を使い、意味が同じ部品を再利用する。

{
  "$defs": {
    "fileId": {
      "type": "string",
      "minLength": 1,
      "description": "アップロード済みファイルの識別子"
    }
  },
  "properties": {
    "receiptFileId": {
      "$ref": "#/$defs/fileId"
    }
  }
}

再利用単位は「string」ではなく、業務上の意味で切る。`employeeId`と`fileId`が同じ文字列形式でも、同じ部品にはしない。将来の変更理由が異なるからだ。

外部ファイルを参照する場合は、`$id`と参照先の解決方法を決める。スキーマを更新したのに、CIや実行環境が古い参照先を使えば検証結果がずれる。版、配布方法、キャッシュ、廃止手順を仕様に含める。

変更レビューでは、必須項目の追加、許容値の縮小、`additionalProperties`の変更を破壊的変更の候補として扱う。列挙値の追加も、クライアントが未知の値で停止するなら影響がある。スキーマの差分だけでなく、利用側の実装とテストを見て判断する。

ステップ5:成功例より失敗例を契約テストにする

完成したスキーマは文書として置くだけでなく、CIで検証する。少なくとも次の三種類を用意する。

  1. 通るべき最小入力
  2. 境界値を含む通る入力
  3. 理由を特定できる通らない入力

経費申請なら、0円、文字列の金額、未定義の区分、宿泊先なしの宿泊費、領収書なしの高額申請を失敗ケースにする。単に「invalidになる」だけでなく、どの項目のどの規則に違反したかを確認する。

ケースID入力条件期待結果
SCH-01金額1円成功
SCH-02金額0円`minimum`違反
SCH-03宿泊費で宿泊先なし`required`違反
SCH-04未定義項目あり`additionalProperties`違反
SCH-05日付形式が不正日付形式の検証に失敗

AIテスト自動化は受け入れ条件からで述べた通り、ツール導入より先に合否を固定する。バリデーターを変更したときも同じfixtureを流し、挙動差を検出する。

さらに、APIテストではスキーマ検証の後に認証、権限、状態遷移、冪等性を確認する。JSON Schemaが通ったことは、業務処理が許可されたことを意味しない。

OpenAPI 3.1と組み合わせるときの確認点

OpenAPI 3.1はJSON Schema Draft 2020-12を基礎にしているため、API操作とデータ制約を近い場所で管理できる。OpenAPI SpecificationのSchema Objectには、方言とSchema Objectの扱いが定義されている。

レビューでは次を確認する。

  • OpenAPIの版とJSON Schemaの方言が明示されているか
  • 要求と応答で未知項目の扱いを決めたか
  • `format`の検証方針をツール間で合わせたか
  • 条件分岐がデシジョンテーブルと一致しているか
  • 共通部品の所有者と変更手順があるか
  • 正常系と異常系のfixtureをCIで実行しているか
  • スキーマ外の認可・状態・再試行を別途テストしているか

JSON Schemaの目的は、長い仕様書を短くすることではない。曖昧な判断を見つけ、機械が確認できる条件へ変え、実装とテストで同じ契約を使うことだ。

APIの制約を業務要件と合わせて整理するツールを検討している場合は、Kakusillの紹介ページも参考にしてほしい。

株式会社Atsumellでは、業務フロー、データ、API、権限、例外、受け入れ条件を整理し、AIと開発チームが実装に使える仕様へ落とし込む支援を行っている。APIの入力条件が議事録やコードへ散らばっている場合は、お問い合わせから相談してほしい。

#JSON Schema#API設計#OpenAPI#契約テスト