【JavaScript】map, filter, findの使い方。オブジェクト配列における具体的な活用事例

JavaScript

APIレスポンスやSQL結果など、JavaScriptでは要素がオブジェクトで構成された配列を操作する機会が多々あります。
そこで今回はオブジェクトの配列を操作する際によく利用されるJavaScriptのmap, filter, findについて紹介します。

mapメソッドを利用したオブジェクト配列の操作

mapは関数を利用してオリジナルの配列から新しい配列を作成するメソッドです。

const values = [1, 2, 3]

// 各要素の値が2倍された配列を生成
doubleValues = values.map((value) => value * 2)

console.log(doubleValues)
// [2, 4, 6]

以下のように、オブジェクトのプロパティを加工して新たな配列を作成したい場合mapが活躍します。

const allUsers = [
  {id: 1, name: 'user_1'},
  {id: 2, name: 'user_2'},
  {id: 3, name: 'user_3'},
  {id: 4, name: 'user_4'},
  {id: 5, name: 'user_5'},
  {id: 6, name: 'user_6'},
]

// 各オブジェクトのnameプロパティが要素となる配列を生成
const allUserLists = allUsers.map((user) => user.name)

console.log(allUserLists)
// [ 'user_1', 'user_2', 'user_3', 'user_4', 'user_5', 'user_6' ]


// オブジェクトのnameプロパティのみを取得する
const allUserNames = allUsers.map((user) => {
  return { name: user.name }
})

console.log(allUserNames)
// [
//   { name: 'user_1' },
//   { name: 'user_2' },
//   { name: 'user_3' },
//   { name: 'user_4' },
//   { name: 'user_5' },
//   { name: 'user_6' }
// ]

filterメソッドを利用したオブジェクト配列の操作

filterは条件を満たした要素のみで構成される配列を生成するメソッドです。

const values = [1, 2, 3, 4, 5]

// 偶数のみで構成された配列を生成
const evenValues = values.filter((value) => value % 2 === 0)

console.log(evenValues)
// [2, 4]

以下のように、取得されたオブジェクトをさらに特定の条件で絞り込みたい場合filterが活躍します。

const allUsers = [
  {id: 1, name: 'user_1', isActive: false},
  {id: 2, name: 'user_2', isActive: true},
  {id: 3, name: 'user_3', isActive: false},
  {id: 4, name: 'user_4', isActive: true},
  {id: 5, name: 'user_5', isActive: true},
  {id: 6, name: 'user_6', isActive: false},
]

// isActiveがtrueのオブジェクトのみで構成された配列を生成
const activeUsers = allUsers.filter((user) => user.isActive)

console.log(activeUsers)
// [
//   { id: 2, name: 'user_2', isActive: true },
//   { id: 4, name: 'user_4', isActive: true },
//   { id: 5, name: 'user_5', isActive: true },
// ]

findメソッドを利用したオブジェクト配列の操作

findは条件を満たす要素を返すメソッドです。

const values = [1, 2, 3, 4, 5]

// 3の要素を取得
const three = values.find((value) => value === 3)

console.log(three)
// 3

filterと違い、findは条件を満たした最初の要素のみを返します。

const values = [1, 2, 3, 4, 5]

// 偶数の要素を取得。条件を満たす最初の要素の『2』のみ取得
const evenValue = values.find((value) => value % 2 === 0)

console.log(evenValue)
// 2

以下のように、idのような重複がない値をもとにオブジェクトを絞り込みたい場合findが活躍します。

const allUsers = [
  {id: 1, name: 'user_1'},
  {id: 2, name: 'user_2'},
  {id: 3, name: 'user_3'},
  {id: 4, name: 'user_4'},
  {id: 5, name: 'user_5'},
  {id: 6, name: 'user_6'},
]

// id = 3のオブジェクトのみ取得する
const thirdUser = allUsers.find((user) => user.id === 3)

console.log(thirdUser)
// { id: 3, name: 'user_3' }

参考: オブジェクトのプロパティを関数で利用する際は分割代入で簡潔に記述できる

オブジェクトのプロパティを関数に渡す場合、ES2015の引数分割束縛(argument destructuring)(MDNで説明されているところの分割代入 (Destructuring assignment))のテクニックを利用すると記述が簡略化できます。

オブジェクトのプロパティを利用して新たな配列を生成するケースでは分割代入がオススメです。

コードの比較は以下の通りです。

// 分割代入(引数分割束縛)を利用してない場合
const activeUsers = allUsers.filter((user) => user.isActive)
// 分割代入(引数分割束縛)を利用した場合
const activeUsers = allUsers.filter(({ isActive }) => isActive)

特にmapの場合、分割代入を利用するとShorthand property namesも組み合わせられるので、より簡潔な記述になります。

// 分割代入(引数分割束縛)を利用してない場合
const allUserNames = allUsers.map(( user ) => {
  return { name: user.name }
})
// 分割代入(引数分割束縛)を利用した場合
const allUserNames = allUsers.map(({ name }) => {

  // Shorthand property namesによって { name: name } は { name }に省略可
  return { name }
})

map, filter, findを組み合わせたオブジェクト操作の具体例

今回の紹介したメソッドを組み合わせたオブジェクト操作の具体例について紹介します。

条件を満たすオブジェクトの特定のプロパティのみ取得する

以下のようなUserデータがあるとします。

allUsers = [
  {id: 1, name: 'user_1', isActive: false},
  {id: 2, name: 'user_2', isActive: true},
  {id: 3, name: 'user_3', isActive: false},
  {id: 4, name: 'user_4', isActive: true},
  {id: 5, name: 'user_5', isActive: true},
  {id: 6, name: 'user_6', isActive: false},
]

このとき、isActive = trueを満たすデータを以下のような形で取得したいとします。

activeUsers = [
  { id: 2, name: 'user_2' },
  { id: 4, name: 'user_4' },
  { id: 5, name: 'user_5' }
]

上記のアウトプットはmapfilterを組み合わせることで取得できます。
具体的なコードは以下の通りです。

const allUsers = [
  {id: 1, name: 'user_1', isActive: false},
  {id: 2, name: 'user_2', isActive: true},
  {id: 3, name: 'user_3', isActive: false},
  {id: 4, name: 'user_4', isActive: true},
  {id: 5, name: 'user_5', isActive: true},
  {id: 6, name: 'user_6', isActive: false},
]

// isActiveがtrueのオブジェクトのみ取得する & isActiveプロパティは除く
const activeUsers = allUsers.filter(({ isActive }) => isActive).map(({ id, name }) => {
  return {
    id,
    name
  }
})

console.log(activeUsers)
// [
//   { id: 2, name: 'user_2' },
//   { id: 4, name: 'user_4' },
//   { id: 5, name: 'user_5' }
// ]

1対多で紐づくデータをまとめたオブジェクトを取得する

以下のようなAuthorのデータがあるとします。

authors = [
  {id: 1, name: 'author_1'},
  {id: 2, name: 'author_2'},
  {id: 3, name: 'author_3'},
]

Authorと1対多で紐づくArticleのデータは以下のようになっています。

articles = [
  {id: 1, title: 'article_1', authorId: 1},
  {id: 2, title: 'article_2', authorId: 3},
  {id: 3, title: 'article_3', authorId: 3},
  {id: 4, title: 'article_4', authorId: 5},
  {id: 5, title: 'article_5', authorId: 7},
]

このとき、AuthorとArticleのデータをまとめて以下のような形で取得したいとします。

authorWithArticles = [
  {
    "id": 1,
    "name": "author_1",
    "articles": [
      {
        "title": "article_1"
      }
    ]
  },
  {
    "id": 2,
    "name": "author_2",
    "articles": []
  },
  {
    "id": 3,
    "name": "author_3",
    "articles": [
      {
        "title": "article_2"
      },
      {
        "title": "article_3"
      }
    ]
  }
]

上記のアウトプットはmapfilterを組み合わせることで取得できます。
具体的なコードは以下の通りです。

const authors = [
  {id: 1, name: 'author_1'},
  {id: 2, name: 'author_2'},
  {id: 3, name: 'author_3'},
]

const allArticles = [
  {id: 1, title: 'article_1', authorId: 1},
  {id: 2, title: 'article_2', authorId: 3},
  {id: 3, title: 'article_3', authorId: 3},
  {id: 4, title: 'article_4', authorId: 5},
  {id: 5, title: 'article_5', authorId: 7},
]

const authorWithArticles = authors.map((author) => {

  // articlesの絞り込み
  const authorArticles = allArticles.filter(({ authorId }) => authorId == author.id);

  // articlesプロパティの作成
  let articles = authorArticles.map(({ title }) => {
    return {
      title
    }
  })

  // オブジェクトの作成
  return {
    ...author,
    articles,
  }
})

// ネストしたオブジェクトをログで表示させる
console.log(JSON.stringify(authorWithArticles, null, 2))

// [
//   {
//     "id": 1,
//     "name": "author_1",
//     "articles": [
//       {
//         "title": "article_1"
//       }
//     ]
//   },
//   {
//     "id": 2,
//     "name": "author_2",
//     "articles": []
//   },
//   {
//     "id": 3,
//     "name": "author_3",
//     "articles": [
//       {
//         "title": "article_2"
//       },
//       {
//         "title": "article_3"
//       }
//     ]
//   }
// ]

1対1で紐づくデータをまとめたオブジェクトを取得する

以下のようなUserの基本データがあるとします。

userBases = [
  { id: 1, name: 'user_1' },
  { id: 2, name: 'user_2' },
  { id: 5, name: 'user_5' },
]

Userの基本データと1対1で紐づくUserの詳細データは以下のようになっています。

allUserDetails = [
  { id: 1, detail_hoge: 'user_1_hoge', detail_fuga: 'user_1_fuga', detail_piyo: 'user_1_piyo', user_id: 1},
  { id: 2, detail_hoge: 'user_2_hoge', detail_fuga: 'user_2_fuga', detail_piyo: 'user_2_piyo', user_id: 2},
  { id: 3, detail_hoge: 'user_3_hoge', detail_fuga: 'user_3_fuga', detail_piyo: 'user_3_piyo', user_id: 3},
  { id: 4, detail_hoge: 'user_4_hoge', detail_fuga: 'user_4_fuga', detail_piyo: 'user_4_piyo', user_id: 4},
  { id: 5, detail_hoge: 'user_5_hoge', detail_fuga: 'user_5_fuga', detail_piyo: 'user_5_piyo', user_id: 5},
]

このとき、User情報をまとめて以下のような形で取得したいとします。

users = [
  {
    id: 1,
    name: 'user_1',
    detail_hoge: 'user_1_hoge',
    detail_fuga: 'user_1_fuga',
    detail_piyo: 'user_1_piyo'
  },
  {
    id: 2,
    name: 'user_2',
    detail_hoge: 'user_2_hoge',
    detail_fuga: 'user_2_fuga',
    detail_piyo: 'user_2_piyo'
  },
  {
    id: 5,
    name: 'user_5',
    detail_hoge: 'user_5_hoge',
    detail_fuga: 'user_5_fuga',
    detail_piyo: 'user_5_piyo'
  }
]

上記のアウトプットはmapfindを組み合わせることで取得できます。
具体的なコードは以下の通りです。

const userBases = [
  { id: 1, name: 'user_1' },
  { id: 2, name: 'user_2' },
  { id: 5, name: 'user_5' },
]

const allUserDetails = [
  { id: 1, detail_hoge: 'user_1_hoge', detail_fuga: 'user_1_fuga', detail_piyo: 'user_1_piyo', user_id: 1},
  { id: 2, detail_hoge: 'user_2_hoge', detail_fuga: 'user_2_fuga', detail_piyo: 'user_2_piyo', user_id: 2},
  { id: 3, detail_hoge: 'user_3_hoge', detail_fuga: 'user_3_fuga', detail_piyo: 'user_3_piyo', user_id: 3},
  { id: 4, detail_hoge: 'user_4_hoge', detail_fuga: 'user_4_fuga', detail_piyo: 'user_4_piyo', user_id: 4},
  { id: 5, detail_hoge: 'user_5_hoge', detail_fuga: 'user_5_fuga', detail_piyo: 'user_5_piyo', user_id: 5},
]

const users = userBases.map((userBase) => {
  const userDetail = allUserDetails.find((details) => details.user_id === userBase.id)
  return {
    ...userBase,
    detail_hoge: userDetail.detail_hoge,
    detail_fuga: userDetail.detail_fuga,
    detail_piyo: userDetail.detail_piyo,
  }
})

console.log(users)
// [
//   {
//     id: 1,
//     name: 'user_1',
//     detail_hoge: 'user_1_hoge',
//     detail_fuga: 'user_1_fuga',
//     detail_piyo: 'user_1_piyo'
//   },
//   {
//     id: 2,
//     name: 'user_2',
//     detail_hoge: 'user_2_hoge',
//     detail_fuga: 'user_2_fuga',
//     detail_piyo: 'user_2_piyo'
//   },
//   {
//     id: 5,
//     name: 'user_5',
//     detail_hoge: 'user_5_hoge',
//     detail_fuga: 'user_5_fuga',
//     detail_piyo: 'user_5_piyo'
//   }
// ]

いずれかのIDに一致するオブジェクトを取得する

以下のようなAuthorのデータがあるとします。

authors = [
  {id: 1, name: 'author_1'},
  {id: 2, name: 'author_2'},
  {id: 5, name: 'author_5'},
]

Authorと1対多で紐づくArticleのデータは以下のようになっています。

allArticles = [
  {id: 1, title: 'article_1', authorId: 1},
  {id: 2, title: 'article_2', authorId: 3},
  {id: 3, title: 'article_3', authorId: 3},
  {id: 4, title: 'article_4', authorId: 5},
  {id: 5, title: 'article_5', authorId: 7},
]

このとき、AuthorのIDに一致するArticle、つまりauthor_id1,2,5いずれかのArticleを以下のような形で取得したいとします。

articles = [
  {id: 1, title: 'article_1', authorId: 1},
  {id: 4, title: 'article_4', authorId: 5},
]

上記のアウトプットはmapfilterを組み合わせることで取得できます。
具体的なコードは以下の通りです。

const authors = [
  {id: 1, name: 'author_1'},
  {id: 2, name: 'author_2'},
  {id: 5, name: 'author_5'},
]

const allArticles = [
  {id: 1, title: 'article_1', authorId: 1},
  {id: 2, title: 'article_2', authorId: 3},
  {id: 3, title: 'article_3', authorId: 3},
  {id: 4, title: 'article_4', authorId: 5},
  {id: 5, title: 'article_5', authorId: 7},
]

// authorsのidを取り出し配列にする
const authorIds = authors.map(({ id }) => id) // [1, 2, 5]

// いずれかのIDに一致するものを取り出す
const articles = allArticles.filter(({ id }) => authorIds.includes(id))

console.log(articles)
// [
//   { id: 1, title: 'article_1', authorId: 1 },
//   { id: 2, title: 'article_2', authorId: 3 },
//   { id: 5, title: 'article_5', authorId: 7 }
// ]

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!