JSON Toolkit

チーム協業 FAQ:契約、設定、フィクスチャ

要点:命名スタイル、時刻形式、null の意味論、enum の形、設定形式、フィクスチャの整理、マージ競合。チームが実際に議論する十二のトレードオフです。

中級約 12 分で読めます2026-10-30 公開2026-10-30 更新
チーム協業 FAQ のカバー画像

要点:これらはチームの取り決めで一度だけ決めましょう。{"data":…,"meta":…} エンベロープと HTTP ステータスコード、サイト全体で一つの命名スタイル、タイムゾーン付き RFC 3339 タイムスタンプ、文字列 enum。その後、新しい API はフィールドを追加するだけで、変更も削除もしない同じ契約に沿って進化させます。

これらの質問に唯一の正解はありませんが、チームには一つの答えが必要です。さもないと新しい API のたびに同じ議論が始まります。

1. API 契約

API はどんな構造を返すべきか

よくある三つの形で、それぞれコストがあります。

形 例 利点 欠点
生データ [{...}, {...}] 単純 メタデータを運べない
固定エンベロープ {"code":0,"message":"ok","data":{...}} エラー処理が一律 業務コードが HTTP ステータスを重複させる
リソースエンベロープ {"data":{...},"meta":{...},"links":{...}} 拡張可能、明確な意味論 ネストが一段増える

内部の業務 API では「リソースエンベロープ + 結果を表す HTTP ステータスコード」を優先します。

{
  "data": [{ "id": "1", "name": "例" }],
  "meta": { "total": 128, "page": 2, "pageSize": 20 }
}

エラーには、別の code をネストするのではなく、HTTP ステータスコードと構造化エラーボディを使います。

{
  "error": {
    "type": "validation_failed",
    "message": "リクエストパラメータの検証に失敗しました",
    "details": [
      { "path": "/email", "reason": "形式が正しくありません" }
    ]
  }
}

要点:HTTP ステータスコードがすでに成功か失敗かを表すので、業務コードは冗長です。二つの体系が共存すると、 「201 なのに code が 500」のような不整合がほぼ必然的に生まれます。

裸の配列は避けてください。トップレベルが配列だとページネーション、総件数、カーソルを付けられず、後から足すにはすべての呼び出し元を変えなければなりません。

フィールド名は snake_case か camelCase か

一つ選び、サイト全体に適用し、一つの JSON 文書内で混ぜないでください。

スタイル 一般的なエコシステム 例
camelCase JavaScript / TypeScript / Java userName、createdAt
snake_case Python / Ruby / Go(一部)/ データベース列 user_name、created_at
kebab-case 稀。ハイフン付きキーはアクセスしにくい user-name

基準:

  1. この JSON を誰が消費するか:フロントエンドが主なら camelCase、データパイプラインと Python サービスが主なら snake_case。
  2. データベース列に直接対応するか:テーブル構造をそのまま通すなら snake_case が変換層を省きます。
  3. 既存システムの継続性:既存 API が使うものを保ちます。一貫性が正しさに勝ります。

どうしても変換が必要なら、業務コードに toCamelCase を散らすのではなく、境界(ゲートウェイかフロントエンドのリクエスト層)で行ってください。

時刻フィールドはどの形式を使うべきか

タイムゾーンオフセット付きの RFC 3339 / ISO 8601 文字列を使います。

{
  "createdAt": "2026-10-30T11:37:57+08:00",
  "updatedAt": "2026-10-30T03:37:57Z"
}

重要な点:

  • タイムゾーンを含める:ゾーンなしの 2026-10-30 11:37:57 はタイムゾーンをまたぐ場面で必ず壊れます。
  • Z かオフセットを一貫して使う:どちらも正しいですが、一つの API 内で混ぜないでください。
  • 数値タイムスタンプを避ける:秒かミリ秒か、うるう秒を含むか、チームごとに再解釈が必要です。 コンパクトさが必要ならミリ秒整数を使い、単位をドキュメントに固定します。

createdAt / updatedAt / deletedAt のように At 接尾辞のフィールド名を優先し、 値が期間ではなく時刻点であることが一目で分かるようにします。期間には timeoutMs、ttlSeconds を使い、単位を名前に入れます。

null は null かフィールド省略か

「このフィールドに意味があるか」に依存します。

意味論 形 備考
フィールドは存在するがまだ値がない "name": null 「確認したが空だった」と明示する
フィールドがこのリソースと無関係 省略 たとえば一般ユーザーに vipExpireAt は不要
部分更新(PATCH) 省略 = 変更しない ここでは null だけが「クリア」を意味する

三番目は最も見落とされます。PATCH リクエストで

{ "nickname": null }

は「ニックネームをクリアする」を意味し、リクエストボディから nickname を省くのは「ニックネームに触れない」を意味します。 両方を「クリア」と扱うと、メールを変えたユーザーがニックネームを消してしまいます。

もう一度:undefined を使わないでください。正しい JSON 値ではなく、JSON.stringify がキーを完全に落とすので、 振る舞いは「省略」と一致するのにコードは「明示的に空の値を渡した」ように見えます。

真偽値フィールドはどう命名すべきか

is / has / can / should 接頭辞を使い、否定を避けます。

{
  "isActive": true,
  "hasPassword": false,
  "canEdit": true
}

三つの教訓:

  1. 否定を使わない:isDisabled: false は読み手に一度反転させる。isEnabled: true の方が直接的です。 二重否定(isNotDisabled)は災難です。
  2. 真偽値に数値を使わない:"enabled": 1 はすべての消費者に === 1 を書かせます。
  3. 文字列を使わない:"enable": "true" は JavaScript で真値で、 if (data.enable) は "false" でも真になります。そのようなバグは追跡が極めて難しいです。

enum フィールドは数値か文字列か

明確なストレージや帯域の制約がない限り、文字列を使います。

{ "status": "active" }

{ "status": 1 } と比べて文字列が勝つ理由:

  • ログ、パケットキャプチャ、データベースがマッピング表を参照せず直接読める。
  • enum 値を追加しても既存値の意味が変わらない(数値 enum の途中に挿入すると全部ずれる)。
  • フロントエンドが 1 → 'active' のマッピングを同期して保守する必要がない。

コストは数バイトで、圧縮後はほとんど無視できます。

併せて:文字列 enum 値は小文字+アンダースコア(pending_review)にし、Schema の enum で制約して 不正な値を検証段階で拒否します。

2. 設定とフィクスチャ

設定は JSON、YAML、TOML のどれか

好みではなく目的で選びます。

形式 向く場面 向かない場面
JSON プログラムによる読み書き、厳格な構造データ、Schema 検証 頻繁な手編集(コメント不可)
YAML 深くネストしたデプロイ設定(K8s) 手書きのインデントは誤りやすく、yes/no が真偽値になる
TOML 平坦な依存・ツール設定(Cargo.toml、pyproject.toml) 深いネストの可読性が悪い
JSONC コメントが必要なエディタ設定(tsconfig.json) システム境界を越える

チームでよくある間違いは業務設定を YAML に保存することです。YAML の暗黙型変換(1.0 が数値、 NO が真偽値、2026-10-30 が日付)はパーサー間で異なる結果を生みます。

設定をプログラムで厳格に検証する必要があるなら、JSON + Schema を選び、コメントは _comment フィールドか別ドキュメントで文書化します。

テストフィクスチャはどう整理すべきか

「一つのベースライン + 最小差分」で整理し、テストケースごとに巨大な JSON をコピーしません。

fixtures/
├─ users/
│  ├─ base.json              # ベースラインのユーザーオブジェクト
│  ├─ no-email.json          # email だけが欠けている
│  └─ invalid-age.json       # age だけが範囲外
└─ fixtures.ts               # 読み込みと浅いマージのユーティリティ関数
import baseUser from './users/base.json';

export function makeUser(overrides: Partial<User> = {}): User {
  return { ...baseUser, ...overrides };
}

こうすれば新しいテストケースは差分フィールドだけを書き、ベースラインのフィールドを変えるとすべてのケースが自動で更新されます。

三つの規則:

  1. フィクスチャは Schema 検証を通らなければならない:ベースライン自体が正しいデータであるべきです。
  2. 本番の実データをエクスポートしない:電話番号、ID 番号、住所はすべて個人情報です。
  3. フィクスチャはバージョン管理に入れるが小さく保つ:数十 KB を超える単一ファイルは削るか生成すべきです。

JSON ファイルのマージ競合を減らすには

JSON は競合しやすいです。フォーマッタがファイル全体を一度に再整形するので、二人がそれぞれ一行変えるとファイル全体で競合します。

四つの戦術:

  1. 整形を統一し .editorconfig に追加する:一貫したインデント、行末、末尾改行が無関係な差分を避けます。
  2. キーを並べ替える:アルファベット順に合意し、新しいフィールドが他人の挿入点と衝突しないようにします。
  3. ファイルを分割する:巨大な config.json を領域別に分け、競合面を自然に縮めます。
  4. 「全部再整形」コミットを避ける:整形と業務変更を二つのコミットに分け、レビューを読みやすく保ちます。

チームがロックファイル(pnpm-lock.yaml、package-lock.json)を使うなら、手でマージせず再生成します。

git checkout --theirs pnpm-lock.yaml
pnpm install

バージョン互換性をどう保つか

原則は「追加する、変更しない、削除しない」です。

変更 互換性 方法
任意フィールドを追加 互換 追加する。消費者は未知フィールドを無視する
必須フィールドを追加 非互換 バージョンを上げるか、既定値を提供する
フィールドを削除 非互換 まず非推奨とマークし、呼び出し元を見てから削除
フィールドの型を変更 非互換 新しいフィールド名を足し、移行期間中は二重書き込み
enum の値集合を変更 場合による 値の追加は互換、削除は非互換

支える仕組み:

  • 消費者は「寛容なパース」を使う。未知フィールドを無視し、欠落には既定値を与える。
  • サーバーは古いフィールドをしばらく保ち、レスポンスに Deprecation ヘッダーをヒントとして返す。
  • Schema で新しいフィールドを required にせず、非推奨フィールドに deprecated: true を付ける。

CI で契約をどう検証するか

Schema をコードの一部として扱い、コミットのたびに検証します。

# サンプルデータを Schema で検証する(ajv-cli を使用)
npx ajv-cli validate -s schemas/user.json -d "fixtures/users/*.json" --strict=false

CI で三つを実行します。

  1. Schema 自体の妥当性:$schema 宣言が正しいか、$ref が解決するか。
  2. フィクスチャが Schema に一致するか:テストデータのドリフトを防ぐ。
  3. 契約が後方互換か:前バージョンと比較し、削除されたフィールドや変更された型を確認する。

三番目にはツールの支援(openapi-diff や json-schema-diff など)が必要です。 なければ少なくとも required 配列と properties キー集合の変化をスクリプトで比較してください。

3. クイックリファレンス

質問 推奨
レスポンス構造 {"data":..., "meta":...}。エラーは HTTP ステータスコード
命名スタイル camelCase(フロントエンド優先)か snake_case(データ優先)。サイト全体で一貫
時刻形式 タイムゾーン付き RFC 3339 文字列、At で終わるフィールド名
null 値なしは null、無関係なフィールドは省略、PATCH の省略は「変更しない」
真偽値命名 is/has/can 接頭辞、否定なし、0/1 なし
enum 文字列、小文字とアンダースコア、Schema に enum を追加
設定形式 プログラム検証は JSON、手編集は TOML か JSONC
フィクスチャ 一つのベースライン + 最小差分の上書き。Schema に適合
マージ競合 整形統一、キー並べ替え、ファイル分割
バージョン互換性 追加する、変更しない、削除しない。削除前に非推奨をマーク