何をしたいユーティリティか:「配列のフラット化」
ここでの「フラット化」は、ネスト(入れ子)になっている配列を、浅い配列に“平らにする”処理のことです。
英語だと flatten、flat と呼ばれます。
例えば、こういう配列があります。
const nested = [1, [2, 3], [4, [5, 6]]];
JavaScriptこれを
[1, 2, 3, 4, 5, 6]
JavaScriptのように、「中の配列をほどいて 1 本の配列」にしたい――これがフラット化です。
業務では、「API から配列の配列が返ってくる」「グループごとの結果を最後に 1 本にまとめたい」といった場面でよく使います。
一番基本:Array.prototype.flat の使い方
flat の基本形
最近の JavaScript には、標準で Array.prototype.flat があります。
これは「指定した深さまで配列をフラット化する」メソッドです。
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat();
// デフォルトは depth = 1
// [1, 2, 3, 4, [5, 6]]
JavaScriptflat() だけだと「1 段だけ」フラット化されます。
もっと深くまでフラット化したいときは、引数に「深さ」を指定します。
nested.flat(2);
// [1, 2, 3, 4, 5, 6]
JavaScriptflat(2) は、「最大 2 段までネストをほどく」という意味です。
完全にフラットにしたいとき
ネストの深さがよく分からない、でも「とにかく全部フラットにしたい」というときは、Infinity を渡すのが定番です。
nested.flat(Infinity);
// [1, 2, 3, 4, 5, 6]
JavaScript「どこまでネストしていても、全部ほどく」という意味になります。
実務でよくあるフラット化のパターン
例1:配列の配列を 1 本にまとめる
例えば、ページングされた結果や、グループごとの結果を配列の配列で持っているとします。
const pages = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }, { id: 4 }],
[{ id: 5 }],
];
JavaScriptこれを「全件 1 本の配列」にしたいとき、flat がそのまま使えます。
const all = pages.flat();
// [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]
JavaScript「1 段だけネストしている」典型的なケースです。
例2:グループ化した結果をフラットに戻す
以前やった「グループ化ユーティリティ」で、
「カテゴリごとに配列に分けた」あと、「やっぱり全部 1 本に戻したい」こともあります。
const grouped = {
A: [{ id: 1 }, { id: 2 }],
B: [{ id: 3 }],
};
const values = Object.values(grouped);
// [[{ id: 1 }, { id: 2 }], [{ id: 3 }]]
const all = values.flat();
// [{ id: 1 }, { id: 2 }, { id: 3 }]
JavaScript「オブジェクトの値(配列)を取り出して、それを flat する」というパターンは、集計系でよく出てきます。
flat が使えない環境向け:自前のフラット化ユーティリティ
1 段だけフラットにする flattenOnce
古い環境や、ポリフィルを入れたくない場合のために、自前のユーティリティも書いておきます。
まずは「1 段だけ」フラットにする版です。
function flattenOnce(array) {
if (!Array.isArray(array)) {
return [];
}
const result = [];
for (const item of array) {
if (Array.isArray(item)) {
result.push(...item);
} else {
result.push(item);
}
}
return result;
}
JavaScriptやっていることはシンプルで、「要素が配列なら中身を展開して push、そうでなければそのまま push」です。
これは array.flat()(depth 1)と同じ動きです。
任意の深さまでフラットにする flattenDepth
次に、「指定した深さまでフラットにする」版です。
function flattenDepth(array, depth = 1) {
if (!Array.isArray(array)) {
return [];
}
if (depth <= 0) {
return array.slice();
}
const result = [];
for (const item of array) {
if (Array.isArray(item)) {
const flatItem = flattenDepth(item, depth - 1);
result.push(...flatItem);
} else {
result.push(item);
}
}
return result;
}
JavaScriptここが重要ポイントです。
depth が 0 以下になったら、それ以上はほどかずにそのまま返す。
配列を見つけたら、depth - 1 で再帰的に flattenDepth を呼ぶ。
そうでない要素はそのまま push。
これで、「最大 depth 段までネストをほどく」動きになります。
完全にフラットにする flattenAll
「とにかく全部フラットにしたい」版も書いておきます。
function flattenAll(array) {
if (!Array.isArray(array)) {
return [];
}
const result = [];
for (const item of array) {
if (Array.isArray(item)) {
const flatItem = flattenAll(item);
result.push(...flatItem);
} else {
result.push(item);
}
}
return result;
}
JavaScriptflattenDepth の depth 無限版だと思ってください。
配列を見つけたら、ひたすら再帰的にほどいていきます。
フラット化と map を組み合わせる:flatMap 的なユーティリティ
「map した結果が配列」なときのあるある
業務では、「1 要素から複数の要素を返す map」を書くことがよくあります。
例えば、「1 つの注文から複数の明細行を取り出す」ようなケースです。
const orders = [
{ id: 1, items: [/* 明細配列 */] },
{ id: 2, items: [/* 明細配列 */] },
];
JavaScriptこれを「全明細行の配列」にしたいとき、素直に書くとこうなります。
const mapped = orders.map((order) => order.items);
// [[...], [...]]
const allItems = mapped.flat();
// [...]
JavaScript「map → flat」というセットが毎回出てくるので、
これを 1 つにまとめた flatMap 的なユーティリティを用意しておくと便利です。
flatMap ユーティリティ
function flatMap(array, mapper) {
if (!Array.isArray(array)) {
return [];
}
const result = [];
for (let i = 0; i < array.length; i++) {
const mapped = mapper(array[i], i, array);
if (Array.isArray(mapped)) {
result.push(...mapped);
} else if (mapped != null) {
result.push(mapped);
}
}
return result;
}
JavaScriptmapper は、「要素を受け取って、配列または単一要素を返す関数」です。
返り値が配列なら展開して push、単一要素ならそのまま push、null/undefined は無視しています。
実際の使い方
const orders = [
{ id: 1, items: [{ sku: "A" }, { sku: "B" }] },
{ id: 2, items: [{ sku: "C" }] },
];
const allItems = flatMap(orders, (order) => order.items);
// [{ sku: "A" }, { sku: "B" }, { sku: "C" }]
JavaScript「map してから flat する」というパターンを、1 つの関数に閉じ込めたイメージです。
実務で意識してほしい設計のポイント
「どこまでフラットにするか」を明示する
フラット化は強力ですが、「どこまでほどくか」を決めないと危険です。
特に、構造を完全に失ってしまうと、「どの要素がどこから来たのか」が分からなくなります。
例えば、「ページごとの結果」を完全にフラットにしてしまうと、「どのページに属していたか」が消えます。
それが問題になる場合は、「1 段だけフラット」「2 段だけフラット」のように、depth を意識して使うべきです。
「配列かどうか」をちゃんと判定する
フラット化ユーティリティでは、「配列かどうか」を Array.isArray で判定するのが基本です。typeof value === "object" だと、オブジェクトまでほどいてしまう危険があります。
「配列だけをほどく」「オブジェクトはそのまま」という線引きを、ユーティリティの中でしっかり決めておくと、予期せぬバグを防げます。
「フラット化」と「結合」の違いを意識する
結合(merge)は、「複数の配列を横に並べて 1 本にする」イメージです。
フラット化(flatten)は、「配列の中にある配列をほどいて 1 本にする」イメージです。
配列の配列を扱うとき、「これは結合したいのか」「構造をほどきたいのか」を意識して、merge 系ユーティリティと flatten 系ユーティリティを使い分けると、コードの意図がとてもクリアになります。
少し手を動かして感覚をつかむ
コンソールで、次のようなコードを実際に打ってみてください。
const nested = [1, [2, 3], [4, [5, 6]]];
nested.flat();
nested.flat(2);
nested.flat(Infinity);
flattenOnce(nested);
flattenDepth(nested, 1);
flattenDepth(nested, 2);
flattenAll(nested);
const pages = [
[{ id: 1 }, { id: 2 }],
[{ id: 3 }],
];
pages.flat();
const orders = [
{ id: 1, items: [{ sku: "A" }, { sku: "B" }] },
{ id: 2, items: [{ sku: "C" }] },
];
flatMap(orders, (order) => order.items);
JavaScript「どの程度フラットになっているか」「構造がどこまで残っているか」「flat と自前ユーティリティの挙動の違い」を、自分の目で確認してみてください。
そのうえで、自分のプロジェクトに
export function flattenOnce(...) { ... }
export function flattenDepth(...) { ... }
export function flattenAll(...) { ... }
export function flatMap(...) { ... }
JavaScriptのような関数を置き、
「ネストした配列をほどきたくなったら、必ずこの“フラット化ユーティリティ”を通す」
というルールを作ってみてください。
そうすると、あなたの配列処理は、「場当たり的なネスト処理」から、「意図がはっきりしたデータ変換」に一段ステップアップしていきます。
