エディタでの入力補完だけに頼っていたところ、レビュアーが見落とした状態のJSON設定がマージされ、本番投入直前のステージング環境で検証エラーが出て青ざめたことがあります。それ以来「人の目」と「CIでの機械チェック」は必ず両方用意する、を徹底するようになりました。最終回では、その仕組み化までをまとめます。

実践例 — ページネーション付きAPIレスポンスの設計

PART 04までの内容を踏まえ、一覧取得APIのレスポンスを例に設計してみます。件数が可変な一覧は配列、メタ情報は固定キーのオブジェクトという使い分けです。

JSON — ページネーション付きレスポンス例
{
  "data": [
    { "id": "1001", "name": "商品A", "price": 1200 },
    { "id": "1002", "name": "商品B", "price": 980 }
  ],
  "pagination": {
    "page": 1,
    "perPage": 20,
    "totalItems": 42,
    "totalPages": 3
  }
}

一覧本体はdataキーの配列にまとめ、ページ番号や総件数など「常に1組しか存在しないメタ情報」はpaginationという固定キーのオブジェクトに分離しています。IDを文字列にしているのはPART 03で触れた大きな整数の精度問題を避けるためです。この構造に対して、PART 04と同じ要領でJSON Schemaを1つ用意しておけば、フロントエンド・バックエンド双方が同じ契約を参照できます。

ローカルでの検証 — pre-commitフック

コミット前にローカルで検証できれば、CIで失敗する前に気づけます。Gitのpre-commitフックに構文チェックとスキーマ検証を仕込む例です。

Shell — .git/hooks/pre-commit(抜粋)
#!/bin/sh
for f in $(git diff --cached --name-only | grep '\.json$'); do
  jq empty "$f" || { echo "構文エラー: $f"; exit 1; }
done
ajv validate -s schemas/response.schema.json -d "data/response.json" || exit 1

CI(GitHub Actions)への組み込み

Pull Request作成から構文チェック・スキーマ検証を経てマージ許可または阻止に至るCIパイプラインの流れを示す図

図1: 構文チェックとスキーマ検証の2段階をCIに組み込み、機械的にマージ可否を判定する

ローカルのpre-commitフックは開発者が任意にスキップできてしまうため、最終防衛ラインとしてCIにも同じチェックを組み込みます。

YAML — .github/workflows/json-validate.yml
name: JSON Validate

on:
  pull_request:
    paths:
      - "**/*.json"

jobs:
  json-validate:
    runs-on: ubuntu-latest
    steps:
      - name: リポジトリをチェックアウト
        uses: actions/checkout@v4

      - name: Node.js のセットアップ
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: jq のインストール確認
        run: jq --version

      - name: 構文チェック(全JSONファイル)
        run: |
          find . -name "*.json" -not -path "./node_modules/*" \
            -exec sh -c 'jq empty "$1" || exit 1' _ {} \;

      - name: ajv-cli のインストール
        run: npm install -g ajv-cli ajv-formats

      - name: スキーマ検証
        run: ajv validate -s schemas/response.schema.json -d data/response.json

このワークフローは*.jsonを含むPull Requestが作成・更新されるたびに実行され、構文エラーまたはスキーマ違反があればジョブが失敗としてマークされます。

ブランチ保護ルールとの組み合わせ

Required status checksに指定する

GitHubリポジトリの Settings → Branches → Branch protection rules で「Require status checks to pass before merging」を有効にし、このjson-validateジョブを必須チェックに指定します。こうすることで、レビュアーが見落としても壊れたJSONやスキーマ違反のデータがmainブランチに混入すること自体を仕組みとして防げます。

⚠️ スキーマ自体もレビュー対象にする

スキーマファイルの変更は「検証ルールそのものの変更」であり、通常のコード変更以上に慎重なレビューが必要です。additionalProperties: falseを緩めるような変更は、意図しないデータの混入を許してしまう可能性がある点に注意してください。

シリーズ全体のまとめ

全5回を通して、JSONの特徴からツール導入、基本文法、構造設計・スキーマ検証、そしてCIでの自動化までを解説してきました。

PART内容
01JSONの特徴、用途、YAML/XMLとの比較
02整形・検証ツールとエディタ拡張機能の導入
03基本文法とデータ型、実務で誤りやすいポイント
04構造設計の指針とJSON Schemaによる検証自動化
05(本記事)実践例とCI組み込み、シリーズまとめ

💡 最初の一歩は「jq empty をCIに1つ置くこと」

スキーマ設計やCI全体を作り込む前に、まずは構文チェックだけをCIに追加するところから始めれば、少なくとも「壊れたJSONがマージされる」事故は防げます。スキーマによる型・値域の検証は、運用しながら段階的に育てていけば十分です。

読んでいただきありがとうございました。ここで扱いきれなかったJSON Patch・JSON Merge Patchによる部分更新の設計や、大規模JSONのストリーミングパースについては、別シリーズで改めて扱う予定です。