JavaScript

JavaScriptのObject.groupBy():配列のデータを担当別にグループ化する

この記事でわかること

Object.groupBy()で配列を担当別に分類する方法を解説。引数と戻り値、ブラウザーとNode.jsの対応・実行手順、参照共有や非同期処理の注意点を自己完結したコードで学びます。

問い合わせ一覧を担当チームごとに表示したいとき、グループ用の配列を作り、該当するデータを追加する処理が必要になります。Object.groupBy()を使うと、各データの「所属先を決める関数」を渡すだけで、グループ別の配列を持つオブジェクトを作れます。

この記事では、問い合わせを担当別に分類する例を通して、呼び出し方と実務で間違えやすい点を学びます。外部ライブラリーや通信は不要です。

何を解決するAPIなのか

Object.groupBy()は、配列などの反復可能な値を受け取り、コールバックが返すキーごとに要素をまとめる静的メソッドです。配列自身ではなく、Objectから呼び出します。

例えば、担当が開発の問い合わせをdev、サポートの問い合わせをsupportというキーにまとめれば、担当別の一覧を取り出せます。元の問い合わせを削除したり、担当を変更したりする必要はありません。

分類結果には各グループの要素が残ります。件数や合計だけが必要なら、その後に集計する設計になります。分類と集計を分けることで、一覧表示と件数表示に同じ結果を使えます。

動作環境と対応バージョン

2026年9月27日時点で確認した主要ブラウザーの対応開始バージョンは、Chrome・Edge 117、Firefox 119、Safari 17.4です。対応状況はweb.devの2024年3月の更新記事で確認できます。古いブラウザーでは、そのまま呼び出せるとは限りません。

Node.jsでも利用できます。配列グループ化はNode.js 21のリリース告知で導入されています。ただし、21系はすでにサポート終了です。本記事を試す環境には、公式リリース一覧でLTSとして掲載されている24系を使用してください。22系でもこのAPIを利用できます。

以下のサンプルはブラウザーとNode.jsの両方に対応します。DOMやファイル操作は使わず、結果をconsole.log()で出力します。実行時にはtypeof Object.groupBy === "function"でAPIの存在も確認します。

引数と戻り値を理解する

呼び出しはObject.groupBy(items, callbackFn)という形です。

  • itemsは分類する反復可能な値です。この記事では配列を渡します。
  • callbackFnは各要素に対して実行される関数です。引数として現在の要素と、0から始まる処理位置を受け取ります。
  • コールバックの戻り値がグループのキーになります。文字列またはSymbolを使用でき、それ以外の値は文字列に変換されます。
  • API全体の戻り値は、各キーに要素の配列を持つ、プロトタイプがnullのオブジェクトです。

これらの仕様はMDNのObject.groupBy()リファレンスに記載されています。

コールバックが決めるのは所属先

コールバックは要素を加工して返す関数ではありません。ticket => ticket.teamと書くと、返された担当名が分類先となり、グループの配列には元の問い合わせが入ります。

担当が未設定ならunassignedを返す、といった分類ルールも書けます。本記事では、担当が文字列で、前後の空白を除いても文字が残る場合だけ担当名を採用します。それ以外は未割り当てに分類します。

自己完結したコードを実行する

次のコードは、そのまま一式をコピーして利用できます。問い合わせ4件を分類し、グループごとのIDと件数を表示します。

JavaScript
(() => {
  if (typeof Object.groupBy !== "function") {
    console.log("この環境はObject.groupBy()に対応していません。");
    return;
  }

  const tickets = [
    { id: "T-101", team: "dev", title: "検索結果の修正" },
    { id: "T-102", team: "support", title: "利用方法の案内" },
    { id: "T-103", team: "dev", title: "表示速度の改善" },
    { id: "T-104", team: null, title: "担当の確認" }
  ];

  const groups = Object.groupBy(tickets, (ticket) => {
    const team = typeof ticket.team === "string"
      ? ticket.team.trim()
      : "";
    return team || "unassigned";
  });

  const displayOrder = ["dev", "support", "unassigned"];

  for (const team of displayOrder) {
    const members = groups[team] ?? [];
    const ids = members.map((ticket) => ticket.id).join(", ");
    console.log(`${team}: ${ids} (${members.length}件)`);
  }

  console.log(`元の件数: ${tickets.length}`);
  console.log(`salesの件数: ${(groups.sales ?? []).length}`);
  console.log(`プロトタイプはnull: ${Object.getPrototypeOf(groups) === null}`);
})();

即時実行関数で囲んでいるため、ブラウザーのコンソールで繰り返し実行しても、同じ名前の変数宣言が衝突しにくい構成です。

ブラウザーでの実行方法

対応ブラウザーで通常のWebページを開き、開発者ツールのConsoleにコード全体を入力して実行します。Chrome・Edgeではメニューから開発者ツールを開き、Consoleを選べます。

このコードはページの内容を変更しません。コンソールが評価結果として最後に表示するundefinedは、ログとは別の表示です。

Node.jsでの実行方法

Node.js 24系をインストールした環境で、コード全体をUTF-8のgroup-tickets.cjsとして保存します。ターミナルで保存先のフォルダーを開き、次を実行してください。

Shell
node --version
node group-tickets.cjs

最初のコマンドでバージョンを確認し、次のコマンドでファイルを実行します。パッケージの追加やpackage.jsonの作成は不要です。

期待される出力

対応環境では、両方の実行方法で次のログになります。

CODE
dev: T-101, T-103 (2件)
support: T-102 (1件)
unassigned: T-104 (1件)
元の件数: 4
salesの件数: 0
プロトタイプはnull: true

T-101とT-103は同じ担当なので、同じ配列に入ります。salesに該当する問い合わせはなく、そのプロパティは作られません。そこで?? []を使い、存在しないグループを空配列として扱っています。

表示順はdisplayOrderで明示しています。この例は担当の種類が固定された画面向けです。実データに別の担当名が増える場合は、表示対象も更新するか、生成されたキーを列挙する処理に変更してください。

失敗例と押さえておきたい制約

分類結果は独立したコピーではない

分類先の配列は新しく作られますが、中のオブジェクトは元データと共有されます。MDNも、要素が深くコピーされないことを説明しています。

例えば、メインのサンプルでgroups.dev[0].titleを書き換えると、tickets[0].titleも変わります。分類後の表示データを編集する設計では、元の状態まで変更しないか確認してください。分類するだけなら、このサンプルのコールバックは元データを変更しません。

戻り値には継承されたメソッドがない

プロトタイプがnullなので、groups.hasOwnProperty("dev")のような呼び方は使えません。グループの存在確認にはObject.hasOwn(groups, "dev")、取り出し時の既定値にはサンプルのような?? []が使えます。

また、コールバックの値が数値の1でも文字列の"1"でも、キーは同じ文字列になります。分類キーは、用途に合う文字列へ明示的にそろえると意図が伝わります。

不正な入力は例外になる

nullのように反復できない値を渡すと、分類結果ではなく例外が発生します。次のコードは対応環境で単独実行できる失敗例です。

JavaScript
(() => {
  if (typeof Object.groupBy !== "function") {
    console.log("Object.groupBy()未対応");
    return;
  }

  try {
    Object.groupBy(null, (value) => value);
  } catch (error) {
    console.log(error instanceof TypeError);
  }
})();

期待出力はtrueです。例外メッセージの文面は環境により異なるため、ここでは例外の種類だけを確認します。コールバック内で例外を投げた場合も呼び出し全体が失敗し、完成した分類結果は返りません。

外部から受け取ったデータでは、配列であることに加え、各要素が想定するオブジェクトかも確認してください。メインの例は担当の未設定には対応しますが、問い合わせ自体がnullという入力までは扱っていません。

非同期コールバックの完了は待たない

このAPIは同期処理です。async関数をコールバックに渡しても、その完了を待って分類してくれるわけではありません。通常のPromiseがキーとして文字列化され、意図しないグループにまとめられる原因になります。

担当名の取得に通信が必要なら、先に非同期処理を完了させ、担当名を持つ配列を作ってから分類します。通信エラーもその段階で扱ってください。サンプルには非同期処理がなく、未処理のPromise拒否は発生しません。

活用事例と導入の判断

担当別の問い合わせ一覧のほか、記事のカテゴリ別表示や、注文の状態別表示にも使えます。分類キーを返す部分を業務ルールに合わせれば、後続の表示処理を整理できます。

実際に導入するときは、未設定値の扱い、表示対象と順序、分類後にデータを編集するかを先に決めると、実装が安定します。特に未設定を単に欠落として扱うと、対応すべき問い合わせを画面から見落とすため、この例では専用のグループを用意しました。

全件を処理してグループの配列を保持するので、大量データでは処理時間とメモリーも確認してください。件数だけが必要な処理なら、要素の配列をすべて保持する設計が適切か検討します。

公式参考資料

掲載コードと期待出力は、公式記述に照らして構文と処理順を机上確認したものです。ブラウザーやNode.jsでの実機実行は行っていません。

PREFERRED SOURCES

Googleの優先するニュース提供元にSEの部屋を追加

Googleで、いつも読みたい情報源を選べます。登録可否はGoogleの画面で確認できます。

Googleの設定画面で確認する 新しいタブで開きます

候補にSEの部屋が表示されない場合は、まだ追加できません。

y.
WRITTEN BY

y_ymo10

SEの部屋で、HTML・CSSからJavaScript・React・Next.jsまで、Web制作の開発ノートを公開しています。

ほかの開発ノートを読む →