JavaScriptで、取得したデータとマスターデータを突き合わせて、データを追加して新しい配列を作る。
es2015以降、JavaScriptには便利な配列操作のメソッドが追加されました。mapとかfilterとかです。その後も新しいものが追加されたりします。es2019のflatMapとか。
これらメソッドの利点は、一目で何をやっているのかわかるところです。
単純に配列を作るとか、条件に合うものだけを持ってくるとか、配列に対して使っているメソッドを見るだけで一目瞭然なのがとても良いです。
ただ、筆者はforの二重ループで配列を混ぜ混ぜしたい時とか、マスターデータがあって、飛んできたデータをマスターデータと突き合わせてデータを足す、みたいな処理の時に、mapなどで書こうとして良く戸惑います。
今回は「あれ? これどうやってmapで書くの?」と筆者が良く一瞬思考が止まるものの1つをメモしておこうと思います。
きっともっと良い例があると思います。いいのあるよという方にはぜひzennで記事を書いてもらって、それを教えてもらえればと思います。
前提条件と求める結果
とあるコードの配列が送られてきたとして、元のオブジェクトのコードと一致するデータを探して、データの一部を持ってきて、最終的に新しいオブジェクトの配列にしたい時の操作です。
こんな配列が送られてきたとします。
const partyLegendCode = ["101","106","119"]
こういう配列(要素がオブジェクト)がマスターデータとしてあります。
const legendList = [
{ "code": "101", "name": "ブラッドハウンド", "voice": "木下紗華" },
{ "code": "102", "name": "ジブラルタル", "voice": "間宮康弘" },
{ "code": "103", "name": "ライフライン", "voice": "羽飼まり" },
{ "code": "104", "name": "パスファインダー", "voice": "川田紳司" },
{ "code": "105", "name": "レイス", "voice": "小島幸子" },
{ "code": "106", "name": "バンガロール", "voice": "三木美" },
{ "code": "107", "name": "コースティック", "voice": "滝知史" },
{ "code": "108", "name": "ミラージュ", "voice": "高橋英則" },
{ "code": "109", "name": "オクタン", "voice": "菊地達弘" },
{ "code": "110", "name": "ワットソン", "voice": "近藤唯" },
{ "code": "111", "name": "クリプト", "voice": "増元拓也" },
{ "code": "112", "name": "レヴナント", "voice": "不明" },
{ "code": "113", "name": "ローバ", "voice": "御沓優子" },
{ "code": "114", "name": "ランパート", "voice": "石井未紗" },
{ "code": "115", "name": "ホライゾン", "voice": "有賀由樹子" },
{ "code": "116", "name": "ヒューズ", "voice": "後藤光祐" },
{ "code": "117", "name": "ヴァルキリー", "voice": "土井真理" },
{ "code": "118", "name": "シア", "voice": "櫻井トオル" },
{ "code": "119", "name": "アッシュ", "voice": "行成とあ" },
{ "code": "120", "name": "マッドマギー", "voice": "橘U子" }
]
出力はこうしたい、というやつです。
const partyLegendList = [
{code: "101", name: "ブラッドハウンド"},
{code: "106", name: "バンガロール"},
{code: "119", name: "アッシュ"},
]
C言語からプログラミングに入った筆者はすぐにforのiでループする外側とjでループする内側の二重ループを考えてしまいます。ループの中で条件にあったものを最終的な配列に追加していくあれです。
ES2015以降のJavaScriptを勉強しだしたら、ネット上のいろんなところでJavaScriptのforが悪者にされているのを見ました…。悲しい。
でも、forは確かに読みづらいですよね。アイテム見逃した警察みたいにfor警察が現れる気持ちもわかります。
mapとfindを使ってみた
とりあえずぱっと見でわかりやすい形を目指して、mapとfindで書いてみました。
変数名が長いのでちょっと見づらいですが、そこを工夫すればもっと読みやすくなります。
const partyLegendList = partyLegendCode.map((legendCode) => {
const findLegend = legendList.find((party) => legendCode === party.code);
return {
code: findLegend?.code,
name: findLegend?.name,
};
});
説明ですが、まず、partyLegendCodeに対してmap()を適用します。
mapでは配列を1つずつ取り出して、処理をして、結果を配列に追加していきます。その処理の時にfindを使います。
findは配列の中で条件が合致した最初のものを1つ取り出すメソッドです。
条件に合致したオブジェクトをそのまま持ってくる場合はfindの結果をそのままreturnすれば良いのですが、今回は必要なデータだけを持ってきたいです。
そのため、mapのreturnとして、オブジェクトを設定して返します。
findは何もないとundifindを返しますが、今回はリストの中に当てはまる物が必ずあるという前提のため、Non-null assertion operatorの?
をつけています。
他の書きかた
forで書くなら
昔ながらの二重ループは以下のような感じです。
大量のデータを取り扱う時はforの方が良いのかもですね~。
const partyLegendList = [];
for (let i = 0; i < partyLegendCode.length; i++) {
for (let j = 0; j < legendList.length; j++) {
if (partyLegendCode[i] === legendList[j].code) {
partyLegendList.push({
code: legendList[j].code,
name: legendList[j].name,
});
}
}
}
for…ofで書くなら
for…ofでも一応動くみたいですけど、これはどうなんでしょうね~。
forEachも似た感じで書けますね。
for (const party of partyLegendCode) {
for (const list of legendList) {
if(party === list.code){
partyLegendList.push({
code: list.code,
name: list.name,
});
}
}
}
for…ofはTypeScriptで書くとき、Array型かstring型でないとエラーが出るんですよね。以前、NodeListを取り扱おうとしてエラーが出てしまって困った時がありました。それで色々ネットで調べた時に、何かで見たのですが、これは仕様のようです。今後変わるかもしれませんが。
元々のデータの問題
元々のデータをもっと扱いやすい形にすればいいんじゃない?というのはごもっともでして、データを設計するときに、処理しやすい形にするのが一番良いのだと思います。
ただ、外部のapiからデータ持ってくるよ、なんて時には元データをいじるのは無理なわけですよね~。そういう場合は、実現できる書き方を思いつかないといけないわけで、たくさん知識の引き出しがあると良いな~と思います。
データの種類によって大量にネストされてるものとかあったりしますよね。そういうのは結構困ります。
Next.jsでブログを作るときに、カテゴリとかタグの機能を追加してみたのですが、データをこねこねしないと行けなくて、JavaScriptの配列処理って難しいな~と思いました。今でもよく苦しみます。ワードプレスって結構めんどくさいことしてるんだな~とカテゴリやタグの管理画面とurl構造を見ながら、ちょっと感心しました。
Discussion
通常、filter と map を使うと思います。
追記:
for警察じゃなく、filter警察になってしまいました。m(.. )m
実例ありがとうございます!
上げていただいた例の方が何をやっているのかわかりやすいですね~。
filter先map後はgoogle検索していたら良く出てきたのですが、includesが思いつかなかったので諦めてました。
あとmapの引数の書き方もすごく勉強になりました!ありがとうございます!