前回書いた記事の続きみたいな記事です。前回はSQLのWindow関数を使って連番取得をしましたが、JavaScriptでもflatMapとか使えばいい感じに出そうでは?と思ったので、試してみた。
やりたいこと
前回の記事で書きましたが、ここでも説明しておくと、以下のような階層構造を持つデータに対して、枝番付きの連番が付いた状態でWebアプリ上の画面に表示したい、という要件です。
階層構造を持つデータの例
id | name | parent_id |
---|---|---|
1 | Web | null |
2 | HTML | 1 |
3 | CSS | 1 |
4 | JavaScript | 1 |
5 | DB | null |
6 | SQL | 5 |
7 | DB設計 | 5 |
8 | インフラ | null |
9 | Linux | 8 |
10 | AWS | 8 |
画面上に表示したい内容
No | カテゴリ名 |
---|---|
1 | Web |
1-1 | HTML |
1-2 | CSS |
1-3 | JavaScript |
2 | DB |
2-1 | SQL |
2-2 | DB設計 |
3 | インフラ |
3-1 | Linux |
3-2 | AWS |
JavaScriptによる実装
途中、色々と試行錯誤しましたが、最終的に以下のコードになりました。
// データ用
const categories = [
{id: 1, name: 'Web', parentId: null},
{id: 2, name: 'HTML', parentId: 1},
{id: 3, name: 'CSS', parentId: 1},
{id: 4, name: 'JavaScript', parentId: 1},
{id: 5, name: 'DB', parentId: null},
{id: 6, name: 'SQL', parentId: 5},
{id: 7, name: 'DB設計', parentId: 5},
{id: 8, name: 'インフラ', parentId: null},
{id: 9, name: 'Linux', parentId: 8},
{id: 10, name: 'AWS', parentId: 8}
]
// filter + map + flatMapで枝番付きの連番を取得
const newCategories = categories.filter(category => category.parentId === null)
.map((parent, parentIndex) => {
return {
index: `${parentIndex + 1}`,
...parent,
children: categories.filter(child => child.parentId === parent.id)
.map((child, childIndex) => ({index: `${parentIndex + 1}-${childIndex + 1}`, ...child}))
}
})
.flatMap(parent => [parent, ...parent.children])
// 確認
console.log(newCategories)
まず、元のデータを持つ配列に対してfilter
で親となるデータのみにする。その後map
を使って、childrenを追加しつつ、mapの引数であるindexを使って連番を生成。最後にflatMapを使って1次元配列に戻すと、欲しかった形式のデータが得られた。
実行結果は以下。indexの部分が画面に表示したい、枝番付きの連番になります。
他にもいろいろとやり方はあるかと思いますが、私の中ではこのコードがうまくまとまったかなと思います。
0: {index: '1', id: 1, name: 'Web', parentId: null, children: Array(3)}
1: {index: '1-1', id: 2, name: 'HTML', parentId: 1}
2: {index: '1-2', id: 3, name: 'CSS', parentId: 1}
3: {index: '1-3', id: 4, name: 'JavaScript', parentId: 1}
4: {index: '2', id: 5, name: 'DB', parentId: null, children: Array(2)}
5: {index: '2-1', id: 6, name: 'SQL', parentId: 5}
6: {index: '2-2', id: 7, name: 'DB設計', parentId: 5}
7: {index: '3', id: 8, name: 'インフラ', parentId: null, children: Array(2)}
8: {index: '3-1', id: 9, name: 'Linux', parentId: 8}
9: {index: '3-2', id: 10, name: 'AWS', parentId: 8}
length: 10
まとめと悩み
flatMap
は関数型プログラミングの文脈で何度か見たことがあったけれど、実際の開発で使ったことがなかったので、今回実際にコードを書いてみて色々と勉強になりました。今後も使える場面はありそうだし、慣れると便利そうです。
ただ、実際の開発要件で上記の方法を使うかどうかは悩ましいところ。
今回の枝番付き連番を表示したいという要件は、画面表示のみに関わる部分なので、そういう意味ではJavaScriptなどのフロントエンドに近い部分で実装した方が責務の観点では良さそうです。しかし、今回対象となるWebアプリがSPAではないため、JavaScriptで画面表示を良い感じに制御するのは結構大変です。(JavaScriptでの実装方法記事書いといてなんですが。。)
バックエンドはJavaなのでJava側で同じことを実現することも可能ですが、Javaだとスプレッド構文が使えなかったり、事前に型定義が必要だったりと、JavaScriptと比較するとコード量が膨大になりそうなので、これはこれで大変そう。
結局SQLで取得するのが最も楽な気はするけれど、DB側でデータ取得時に連番も取得する場合、DBから取得したデータを格納するモデル(エンティティ)にも、それ用のフィールド(プロパティ)を追加してあげる必要がある。画面表示にしか使わない項目に対して、DB側のモデルにも項目を用意するのもちょっと微妙だなという感じがしてしまう。
別のアプローチとして、DBからデータを取得する時点で親と子のデータを別々に取得して最初から階層構造でデータを保持するデータ構造にしておく。そうすれば、Java側でflatMapを使えば比較的少ないコード量で収まるかもしれません。ただ、現状動作しているコードに対して修正コストが大きくなってしまうので、それはそれで難しい。
一般的にプログラミングはやりたいことに対して複数のアプローチがありますが、どのアプローチで実装するのが良いかと考えるのは楽しくもあり、非常に難しいなといつも思う。このような複数のアプローチがある場面で、どれだけ良い意思決定ができるかどうかが、その人のエンジニアとしての力量なのかなと思います。