ラッパーJSONとフラットJSONの違い|素のPHP関数で実装する実務レシピ

はじめに

APIや非同期通信で扱うJSONには、メタ情報で包む「ラッパーJSON」と、最小構造の「フラットJSON」があります。
本記事では両者の特徴・使い分けと、WordPressに依存しない素のPHP関数だけで実運用できるレスポンス実装(CORS・キャッシュ制御・エラー整形・切り替えクエリ ?flat=1 含む)を示します。


1. 定義とサンプル

ラッパーJSON(Wrapper JSON)

データ本体をメタ情報で包む形式。拡張しやすく、APIの一貫性を保ちやすい。

{
  "status": "ok",
  "message": "success",
  "meta": { "page": 1, "per_page": 20, "total": 137 },
  "data": [
    { "id": 101, "title": "記事A", "published_at": "2025-11-10" },
    { "id": 102, "title": "記事B", "published_at": "2025-11-11" }
  ]
}

フラットJSON(Flat JSON)

メタ情報を持たない、軽量な配列/オブジェクト。

[
  { "id": 101, "title": "記事A", "published_at": "2025-11-10" },
  { "id": 102, "title": "記事B", "published_at": "2025-11-11" }
]

2. 使い分けの要点

指標ラッパーJSONフラットJSON
メタ情報(ページ/総件数/並び順)必要不要
エラー・警告の表現詳細に統一しやすい別チャンネルが必要
将来拡張強い弱い
軽量性
  • 公開API/外部連携/管理画面ではラッパーが安定。
  • 内部用の単純な一覧はフラットで軽量化。
  • 妥協案として?flat=1で切り替え可能にしておくと現場で扱いやすい。

3. 素のPHPでJSONを返すユーティリティ(共通関数)

これだけで wp_send_json なしに全APIを統一できます。
CORSやヘッダは引数で調整可能。exit;で確実に終了。

<?php
/**
 * JSONを出力してスクリプト終了(素のPHP版)
 *
 * @param mixed $payload         返却する配列/オブジェクト
 * @param int   $status          HTTPステータス(例: 200, 400, 422, 500)
 * @param array $extra_headers   追加ヘッダ ['Header-Name' => 'Value']
 * @param int   $json_flags      json_encode フラグ
 */
function json_output($payload, int $status = 200, array $extra_headers = [], int $json_flags = 0)
{
    // 既定のエンコードフラグ(日本語/スラッシュのエスケープ抑止 + 部分失敗許容)
    $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR;
    if ($json_flags) {
        $flags |= $json_flags;
    }

    // ヘッダ未送出なら送る
    if (headers_sent() === false) {
        // 必要に応じてCORS(公開APIならオリジンを適切に限定推奨)
        // $extra_headers['Access-Control-Allow-Origin'] = 'https://example.com';
        // $extra_headers['Vary'] = 'Origin';

        header('Content-Type: application/json; charset=utf-8', true, $status);
        header('X-Content-Type-Options: nosniff');
        // キャッシュ抑止:動的APIの既定値。静的性が高い場合は適宜変更
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');

        foreach ($extra_headers as $k => $v) {
            header($k . ': ' . $v);
        }
    }

    $json = json_encode($payload, $flags);

    // 逆プロキシ/Chunked環境では Content-Length を省く場合あり
    if ($json !== false && headers_sent() === false) {
        header('Content-Length: ' . strlen($json));
    }

    echo $json !== false ? $json : '{"status":"error","message":"json_encode failed"}';
    exit; // 確実に終了(WordPressでも使用可)
}

4. ラッパー/フラットの切り替え実装(?flat=1)

<?php
// 例: /api/posts に紐づくハンドラ内(フレームワーク不問)
// 実データはDBや外部APIから取得してください
$items = [
    ['id' => 101, 'title' => '記事A', 'published_at' => '2025-11-10'],
    ['id' => 102, 'title' => '記事B', 'published_at' => '2025-11-11'],
];

$page    = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1;
$pp      = isset($_GET['per_page']) ? max(1, (int)$_GET['per_page']) : 20;
$total   = count($items); // 実際は総件数クエリ
$is_flat = isset($_GET['flat']) && (string)$_GET['flat'] === '1';

if ($is_flat) {
    // フラットJSON
    json_output($items, 200);
} else {
    // ラッパーJSON
    $response = [
        'status'  => 'ok',
        'message' => 'success',
        'meta'    => ['page' => $page, 'per_page' => $pp, 'total' => $total],
        'data'    => $items,
    ];
    json_output($response, 200);
}

5. エラー・バリデーションの統一形

<?php
// 入力チェック例
$errors = [];
if (empty($_GET['title'])) {
    $errors[] = ['code' => 1001, 'field' => 'title', 'message' => 'タイトルは必須です。'];
}
if ($errors) {
    json_output([
        'status' => 'error',
        'errors' => $errors,
    ], 422);
}

// 正常時
json_output(['status' => 'ok', 'message' => 'saved'], 200);

6. セキュリティ/運用の注意

  • CORS:公開APIは Access-Control-Allow-Origin特定オリジンに限定。認証が絡む場合は資格情報の扱いにも注意。
  • キャッシュ:管理系APIは no-store、一覧のGETは短期 max-age など用途で分離
  • ログjson_output 直前でリクエストIDや所要時間をログ出力すると運用が安定。
  • 例外try/catch で例外を拾い、status:error + 5xx/4xx へ整形してから json_output に集約。

7. 動作確認のための cURL 例

ラッパー(通常)

curl -i "https://example.com/api/posts"

フラット(軽量)

curl -i "https://example.com/api/posts?flat=1"

バリデーションエラー

curl -i "https://example.com/api/posts/create"
# 必須欠落で 422 + {status:error,errors:[...]} を想定

8. 既存プロジェクトへの導入手順(最短)

  1. 共通関数 json_output() をユーティリティに追加(例:api/services/JsonResponse.php でも functions.php でも可)。
  2. ルーター/ハンドラの末尾をjson_output($payload, $status);へ置換。
  3. ラッパー/フラット切替が必要なAPIは、?flat=1 を見て分岐。
  4. エラー時は同一スキーマ{status:error, errors:[...]})で統一。
  5. CORSとキャッシュヘッダは用途ごとに extra_headers で付与。

まとめ

  • ラッパーJSONは拡張性・一貫性が高く、メタ情報やエラーを表現しやすい。
  • フラットJSONは軽量・シンプルで内部用のリストに向く。
  • 素のPHP関数 json_output() を使えば、WordPress依存なしで安定したJSONレスポンスを統一運用できる。
  • 妥協案として ?flat=1 切替を用意しておくと、軽量化ニーズと公開APIの設計品質を両立できる。

コメント

タイトルとURLをコピーしました