JavaScript | 配列・オブジェクト:実務パターン – API レスポンス加工

JavaScript JavaScript
スポンサーリンク

API レスポンス加工とは何か

API レスポンス加工は「外部サービスから受け取ったJSON(欠損・余分・型揺れが混在しがち)を、アプリで使いやすい“整った配列・オブジェクト”に変換する」ことです。ここが重要です:受け取り直後に“検証→正規化→整形→既定値補完→インデックス化(必要なら)”の順で行うと、後段のロジックが短く、安定します。


基本パイプライン(検証→正規化→整形→補完)

スキーマのイメージを持つ(必要フィールドを明確化)

// 期待する形(例)
/*
User {
  id: string | number  // 一意なID
  name: string         // 表示名
  email: string | null // 欠損は null
  createdAt: string    // ISO日付文字列 "YYYY-MM-DDTHH:mm:ssZ"
}
*/
JavaScript

ここが重要です:まず“何を使うか”を決める。レスポンスには余分なフィールドが多いので、使うキーだけを取り出し、欠損や型揺れに対して既定値・変換ルールを持つ。

受け取り後の安全加工(最小セット)

const normalizeText = s => (s == null ? "" : String(s).trim());
const toIso = s => {
  const d = new Date(s);
  return Number.isNaN(d.getTime()) ? null : d.toISOString();
};

function shapeUser(u) {
  return {
    id: u?.id ?? null,
    name: normalizeText(u?.name) || "(unknown)",
    email: u?.email == null ? null : normalizeText(u.email),
    createdAt: toIso(u?.created_at ?? u?.createdAt) // APIの命名揺れを吸収
  };
}

function shapeUsersResponse(res) {
  const list = Array.isArray(res?.users) ? res.users : [];
  return list.map(shapeUser);
}
JavaScript

ここが重要です:?. で欠損に強くアクセスし、?? で null/undefined のときだけ既定値に切り替える。命名の揺れ(snake_case と camelCase)は吸収して、内部表現を統一する。


正規化と辞書化(検索・更新を速くする)

正規化(entities)へ変換してから使う

function normalizeUsers(users) {
  return {
    entities: Object.fromEntries(users.map(u => [String(u.id), u])),
    result: users.map(u => String(u.id))
  };
}

// 使い方
const shaped = shapeUsersResponse(apiRes);
const { entities, result } = normalizeUsers(shaped);
const user123 = entities["123"]; // O(1)で即アクセス
JavaScript

ここが重要です:大量データの一覧表示+部分更新には“辞書+ID配列”が効く。更新は辞書だけ触り、表示時は必要に応じてデノーマライズする。


欠損・型揺れ・表記ゆれへの対処

欠損は null へ寄せる(空文字は使わない)

const emptyToNull = s => {
  const t = s == null ? "" : String(s).trim();
  return t.length ? t : null;
};

// 例:emailを空ならnullへ
const email = emptyToNull(u.email);
JavaScript

ここが重要です:空文字と未入力は意味が違う。サーバーで扱いやすいのは“未入力は null”。この統一で後段の条件分岐が減る。

数値・真偽・日付の安全変換

const toInt = (s, def = null) => {
  const n = Number.parseInt(String(s), 10);
  return Number.isFinite(n) ? n : def;
};
const toBool = v => v === true || v === "true" || v === 1 || v === "1";
const toDateMs = s => {
  const d = new Date(s);
  return Number.isNaN(d.getTime()) ? null : d.getTime();
};
JavaScript

ここが重要です:APIは文字列で返すことが多い。変換に失敗したら“エラーにするか、既定値にするか”を仕様化して一貫させる。


レスポンス構造のよくある加工(ページング・ネスト・結合)

ページングメタと行の分離(UIで使いやすく)

function shapePaged(res) {
  const rows = (res?.data ?? []).map(shapeUser);
  const total = toInt(res?.total, rows.length) ?? rows.length;
  const page = toInt(res?.page, 1) ?? 1;
  const perPage = toInt(res?.perPage, rows.length) ?? rows.length;
  const totalPages = Math.max(1, Math.ceil(total / perPage));
  return { rows, meta: { total, page, perPage, totalPages } };
}
JavaScript

ここが重要です:一覧とメタ(総件数、ページ番号)を分ける。UIは rowsmeta を別々に使えるほうが見通しが良い。

ネストの正規化(ユーザー・コメント・投稿)

function normalizePost(post) {
  const users = {};
  const comments = {};
  const commentIds = [];

  const addUser = u => (users[u.id] = { id: u.id, name: normalizeText(u.name) });

  if (post?.author) addUser(post.author);

  for (const c of post?.comments ?? []) {
    addUser(c.author);
    comments[c.id] = { id: c.id, authorId: c.author.id, text: normalizeText(c.text) };
    commentIds.push(c.id);
  }

  const posts = {
    [post.id]: { id: post.id, authorId: post.author?.id ?? null, commentIds }
  };
  return { entities: { users, comments, posts }, result: post.id };
}
JavaScript

ここが重要です:重複エンティティを1箇所に集約(辞書化)し、関係はIDで持つ。更新・検索・差分検知が格段に楽になる。

APIの分割レスポンスを結合(JOIN風)

function joinPostWithUsers(post, usersDict) {
  const author = usersDict[post.authorId] ?? null;
  const comments = post.commentIds.map(id => usersDict[id] ? id : id); // 例: 参照チェック
  return { ...post, author, comments };
}
JavaScript

ここが重要です:別エンドポイントから来た関連データは“キー(ID)”で結合する。揺れや欠損を許容しつつ、UIで必要な形へ復元(デノーマライズ)する。


エラー・ステータス・バージョンの扱い

成功・失敗を分けて返す(呼び出し側で楽に)

function shapeResult(res) {
  const ok = res?.status === 200 || res?.ok === true;
  if (!ok) {
    const code = res?.error?.code ?? "unknown";
    const msg = normalizeText(res?.error?.message) || "unexpected error";
    return { ok: false, error: { code, message: msg } };
  }
  return { ok: true, data: res.data };
}
JavaScript

ここが重要です:上位は ok で分岐しやすい形が便利。エラーはコード+メッセージに分けるとUIでメッセージ差し替えが容易。

APIバージョン差に耐える(フォールバックの設計)

function shapeUserCompat(u) {
  // v1: created_at, v2: createdAt
  const createdRaw = u?.created_at ?? u?.createdAt ?? null;
  return {
    id: u?.id ?? null,
    name: normalizeText(u?.name) || "(unknown)",
    createdAt: toIso(createdRaw)
  };
}
JavaScript

ここが重要です:命名・型の違いを“変換点”で吸収して、内部のモデルは常に同じ形。呼び出し側のコードを汚さない。


性能の工夫(前計算・部分適用・安全な並び)

キーの前計算で高速化(Schwartzian transform)

const prepared = shaped.map(u => ({
  u,
  nameKey: normalizeText(u.name).toLowerCase(),
  createdMs: toDateMs(u.createdAt) ?? 0
}));

const filtered = prepared
  .filter(p => p.nameKey.includes("alice"))
  .toSorted((a, b) => a.createdMs - b.createdMs)
  .map(p => p.u);
JavaScript

ここが重要です:重い正規化や new Date を比較時に毎回行わない。前計算してから判定・ソートするだけで速度が段違いになる。

非破壊を徹底(元配列を壊さない)

// 並び替えは toSorted、絞り込みは filter
const sorted = list.toSorted((a, b) => a.id - b.id);
const filtered = list.filter(x => x.active);
JavaScript

ここが重要です:APIデータを共有している場面では破壊的メソッド(sort, splice)で状態が壊れやすい。常に新インスタンスを返す。


すぐ使えるレシピ(定番ユーティリティ)

レスポンスの安全取り出し+整形

function shapeListRes(res, shapeItem) {
  const list = Array.isArray(res?.data) ? res.data : (Array.isArray(res) ? res : []);
  return list.map(shapeItem);
}
JavaScript

辞書化(ID→レコード)

const toDict = (list, key = "id") =>
  Object.fromEntries(list.map(x => [String(x[key]), x]));
JavaScript

欠損安全な取得

const get = (obj, path) => path.reduce((acc, k) => acc?.[k], obj);
JavaScript

ページングメタ付きの整形

function shapePagedRes(res, shapeItem) {
  const rows = shapeListRes(res, shapeItem);
  const total = toInt(res?.total, rows.length) ?? rows.length;
  const page = toInt(res?.page, 1) ?? 1;
  const perPage = toInt(res?.perPage, rows.length) ?? rows.length;
  const totalPages = Math.max(1, Math.ceil(total / perPage));
  return { rows, meta: { total, page, perPage, totalPages } };
}
JavaScript

まとめ

API レスポンス加工の核心は「受け取り直後に、検証→正規化→整形→既定値補完→(必要なら)辞書化」を一貫して行うことです。欠損は null、型揺れは安全変換、命名揺れは変換点で吸収。一覧は“行とメタ”を分け、ネストは正規化(entities)で管理し、表示時にデノーマライズ。性能は前計算で最適化し、非破壊で安全に扱う。これを徹底すれば、初心者でも壊れにくく読みやすい“実務で信頼できる”レスポンス加工が書けます。

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