JavaScript

JavaScriptのflatMapとreduceでネストした注文データを集計する

注文データを例に、flatMapでネストした配列を平坦化し、reduceで合計や件数を集計する流れを整理します。

この記事でわかること

  • ネストした配列を集計する前に、データの階層と目的を確認できる
  • flatMapで内側の配列を1つの配列にまとめる考え方を理解できる
  • reduceで平坦化したデータから合計値や件数を作る流れを把握できる
  • flatMapとreduceを組み合わせる際の中間データと確認ポイントを整理できる

ネストした注文データを集計する前に構造を確認する

APIレスポンスや業務データでは、注文の配列の中に商品配列が入る形を扱うことがあります。まず、外側の配列が注文、内側の配列が商品であることを確認し、何を集計するのかを決めます。たとえば、商品ごとの金額を合計する場合は「数量 × 単価」を累積します。商品数を数える場合は、商品配列を平坦化した後の要素数を確認します。

JavaScript
const orders = [
  {
    orderId: "A001",
    items: [
      { name: "キーボード", quantity: 1, unitPrice: 8000 },
      { name: "マウス", quantity: 2, unitPrice: 3000 },
    ],
  },
  {
    orderId: "A002",
    items: [
      { name: "モニター", quantity: 1, unitPrice: 25000 },
    ],
  },
];

// 構造: orders[注文].items[商品]

この例では、注文配列をそのまま集計するのではなく、各注文のitemsを1つの配列にまとめてから商品単位で処理します。

flatMapでネストした商品配列を1つにまとめる

flatMap()は、配列の各要素を変換した結果を1段階平坦化するメソッドです。各注文からitemsを返すと、注文ごとに分かれていた商品配列を1つの配列へまとめられます。map()で同じ処理をすると配列の配列になります。一方、flatMap()では後続処理で商品を直接走査しやすい形になります。

処理結果の階層後続処理
orders.map(order => order.items)商品配列の配列二重ループなどが必要
orders.flatMap(order => order.items)商品オブジェクトの配列商品単位でreduceできる
mapとflatMapの結果の違い
JavaScript
const items = orders.flatMap((order) => order.items);

console.log(items);
// 期待される形:
// [
//   { name: "キーボード", quantity: 1, unitPrice: 8000 },
//   { name: "マウス", quantity: 2, unitPrice: 3000 },
//   { name: "モニター", quantity: 1, unitPrice: 25000 }
// ]

flatMap()の平坦化は1段階です。商品配列のさらに内側に配列がある場合は、データ構造に応じてflat()など別の処理が必要になります。

reduceで平坦化した配列を集計する

reduce()は、配列の要素を順に処理し、累積値から1つの結果を作るメソッドです。合計金額では、累積値に「現在の商品の数量 × 単価」を加えます。初期値として0を指定すると、最初の累積値を明示できます。

JavaScript
const totalAmount = items.reduce(
  (total, item) => total + item.quantity * item.unitPrice,
  0,
);

console.log(totalAmount);
// 期待される結果: 42000

同じ考え方で、合計数量も計算できます。reduce()の累積値を数値にするか、複数の項目を持つオブジェクトにするかは、必要な集計結果に応じて選びます。

JavaScript
const summary = items.reduce(
  (result, item) => {
    result.totalQuantity += item.quantity;
    result.totalAmount += item.quantity * item.unitPrice;
    return result;
  },
  { totalQuantity: 0, totalAmount: 0 },
);

console.log(summary);
// 期待される結果: { totalQuantity: 4, totalAmount: 42000 }

空配列を処理する可能性がある場合は、reduce()の初期値を指定しておくと、集計結果の型や初期状態を明確にできます。初期値を省略する場合の挙動は、利用するデータ条件を確認してから決めます。

flatMapとreduceを組み合わせて注文データを集計する

実際の処理では、flatMapで商品配列を作り、その結果をreduceへ渡します。中間結果を変数に分けると、平坦化の結果と集計結果を個別に確認できるため、デバッグしやすくなります。

JavaScript
const items = orders.flatMap((order) => order.items);

const result = items.reduce(
  (summary, item) => ({
    totalQuantity: summary.totalQuantity + item.quantity,
    totalAmount: summary.totalAmount + item.quantity * item.unitPrice,
  }),
  { totalQuantity: 0, totalAmount: 0 },
);

console.log(result);
// 期待される結果: { totalQuantity: 4, totalAmount: 42000 }

処理の流れは、「注文配列から商品を取り出す」→「flatMapで1つの配列にする」→「reduceで各商品の値を累積する」です。処理に慣れた後は、次のように一続きで書くこともできますが、確認が必要な処理では中間変数を残す方が読みやすい場合があります。

JavaScript
const totalAmount = orders
  .flatMap((order) => order.items)
  .reduce(
    (total, item) => total + item.quantity * item.unitPrice,
    0,
  );

console.log(totalAmount);
// 期待される結果: 42000

実データへ適用するときの確認ポイント

実データへ適用する前に、itemsが必ず配列として存在するか、空配列を許容するかを確認します。quantityやunitPriceが文字列で返る場合は、数値変換の方針も必要です。想定外のnullや欠損値をそのまま計算すると、期待しない結果になる可能性があります。

  • 税込・税抜のどちらを集計するか決める
  • 割引額や送料を合計へ含めるか決める
  • キャンセル済み注文を対象外にするか確認する
  • flatMap後の配列とreduce後の結果を小さなサンプルで確認する
  • 対象のブラウザーやNode.jsなど、実行環境で利用可否を確認する

まずはサンプルデータで中間配列を出力し、商品数や各商品の数量・単価が想定どおりか確認します。そのうえで、対象システムのデータ仕様に合わせて、欠損値や集計条件をコードへ反映します。

参考