Object.groupBy が使えるならそれが最短です。使えない環境向けの reduce での書き方、キーが数値や undefined になる罠、件数だけ数える形までまとめます。
答え
Object.groupBy() に「何をキーにするか」を渡します。
example.js
const users = [
{ name: 'あかり', team: 'A' },
{ name: 'ゆうと', team: 'B' },
{ name: 'はると', team: 'A' },
];
console.log(Object.groupBy(users, u => u.team));
出力書き換えて実行できます
{ A: [ { name: 'あかり', team: 'A' }, { name: 'はると', team: 'A' } ], B: [ { name: 'ゆうと', team: 'B' } ] }
example.js
const nums = [1, 2, 3, 4, 5, 6];
const byMod = Object.groupBy(nums, n => n % 3);
console.log(byMod);
console.log(Object.keys(byMod));
出力書き換えて実行できます
{ '0': [ 3, 6 ], '1': [ 1, 4 ], '2': [ 2, 5 ] }
[ '0', '1', '2' ]
ES2024 です。(Node 21 / Chrome 117 / Safari 17.4 以降)
古い環境も相手にするなら、後述の reduce() で書きます。
上のコードは書き換えて実行できます。u.team を u.name.length にしてみてください。
キーは必ず文字列になる
Object.groupBy() の戻り値はオブジェクトなので、キーは文字列に変換されます。
example.js
const nums = [1, 2, 3, 4, 5, 6];
const byMod = Map.groupBy(nums, n => n % 3);
console.log(byMod);
console.log(byMod.get(0));
console.log(byMod.get('0'));
出力書き換えて実行できます
Map(3) { 1 => [ 1, 4 ], 2 => [ 2, 5 ], 0 => [ 3, 6 ] }
[ 3, 6 ]
undefined
example.js
const users = [
{ name: 'あかり', team: 'A' },
{ name: 'ゆうと', team: 'B' },
{ name: 'はると', team: 'A' },
];
const byTeam = users.reduce((acc, u) => {
(acc[u.team] ??= []).push(u);
return acc;
}, {});
console.log(Object.keys(byTeam));
console.log(byTeam.A.map(u => u.name));
出力書き換えて実行できます
[ 'A', 'B' ]
[ 'あかり', 'はると' ]
数値でキーを引きたいときは Map.groupBy() を使います。キーの型が保たれます。
example.js
const users = [{ name: 'あかり', team: 'A' }, { name: 'ゆうと', team: 'A' }];
const byTeam = users.reduce((acc, u) => {
if (!acc[u.team]) acc[u.team] = [];
acc[u.team].push(u);
return acc;
}, {});
console.log(byTeam.A.length);
出力書き換えて実行できます
2
javascript
const users = [
{ name: 'あかり', team: 'A' },
{ name: 'ゆうと' },
];
const byTeam = Object.groupBy(users, u => u.team);
console.log(Object.keys(byTeam));
console.log(byTeam.undefined.length);
[ 'A', 'undefined' ]
1
Object.groupBy() なら '0'、Map.groupBy() なら 0 で引きます。
取り違えると undefined になります。
|
戻り値 |
キーの型 |
順序 |
Object.groupBy() |
オブジェクト |
文字列になる |
数字に見えるキーが先 |
Map.groupBy() |
Map |
そのまま |
出てきた順 |
オブジェクトをキーにしたいなら Map.groupBy() しかありません。
古い環境で書く
reduce() で同じことができます。中身は「無ければ配列を作って、押し込む」だけです。
example.js
const users = [
{ name: 'あかり', team: 'A' },
{ name: 'ゆうと' },
];
console.log(Object.keys(Object.groupBy(users, u => u.team ?? '未所属')));
console.log(Object.keys(Object.groupBy(users.filter(u => u.team), u => u.team)));
出力書き換えて実行できます
[ 'A', '未所属' ]
[ 'A' ]
example.js
const votes = ['りんご', 'みかん', 'りんご', 'ぶどう', 'りんご'];
const count = votes.reduce((acc, v) => {
acc[v] = (acc[v] ?? 0) + 1;
return acc;
}, {});
console.log(count);
出力書き換えて実行できます
{ 'りんご': 3, 'みかん': 1, 'ぶどう': 1 }
??= は「まだ無ければ入れる」です。書けない環境なら、こう書きます。
example.js
const votes = ['りんご', 'みかん', 'りんご'];
const count = Object.fromEntries(
Object.entries(Object.groupBy(votes, v => v)).map(([k, list]) => [k, list.length]),
);
console.log(count);
出力書き換えて実行できます
{ 'りんご': 2, 'みかん': 1 }
example.js
const sales = [
{ shop: '東京', amount: 1200 },
{ shop: '大阪', amount: 800 },
{ shop: '東京', amount: 300 },
];
const byShop = Object.groupBy(sales, s => s.shop);
const totals = Object.fromEntries(
Object.entries(byShop).map(([shop, list]) => [
shop,
list.reduce((sum, s) => sum + s.amount, 0),
]),
);
console.log(totals);
出力書き換えて実行できます
{ '東京': 1500, '大阪': 800 }
{ ...acc } で作り直さないでください。 要素の数だけ複製することになります。
理由は Array.prototype.reduce() にあります。
キーが undefined になる
キーを取り出す式が空振りすると、undefined という名前のグループができます。
example.js
const rows = [
{ year: 2026, month: 9, n: 1 },
{ year: 2026, month: 9, n: 2 },
{ year: 2026, month: 8, n: 3 },
];
const byMonth = Object.groupBy(rows, r => `${r.year}-${r.month}`);
console.log(Object.keys(byMonth));
console.log(byMonth['2026-9'].map(r => r.n));
出力書き換えて実行できます
[ '2026-9', '2026-8' ]
[ 1, 2 ]
文字列の 'undefined' です。 気づかずに集計に混ざります。
既定値を決めるか、先に弾いてください。
「所属が無い人」を見せたいのか、捨てたいのかを決めてから書きます。
件数だけ数える
グループの中身が要らないなら、配列を作らず数えるほうが安いです。
groupBy で作ってから数えることもできます。読みやすいほうを選んでください。
グループごとに合計する
グループ化してから、各グループを reduce() します。
合計の落とし穴(小数・空配列・文字列が混ざる)は
配列の合計を出す にまとめてあります。
複数のキーでまとめる
キーを繋いだ文字列にします。区切りには、値に出てこない文字を選んでください。
'2026-9' が先に来ています。数字だけのキーなら小さい順に並びますが、'2026-9' は数字ではないので出てきた順です。ここでも順序に頼らないでください。
キーに使う値そのものに区切り文字が入りうるなら、この方法は使えません('a-b' と 'a' + '-b' が衝突します)。そのときは Map.groupBy と入れ子にしてください。