Я забыл все о .findIndex()

Несколько дней назад я увидел этот вопрос на Stackoverflow. Это вопрос JavaScript, в котором программист пытается сгруппировать массив объектов JavaScript по определенному свойству этих объектов. Но они хотели сгруппировать его определенным образом, а именно, аккумулировав данные всех объектов, которые имеют одно и то же свойство tableName свойство, всего в один объект.

Вот их исходные данные:

const data = [
    {
        "dataId": "1",
        "tableName": "table1",
        "column": "firstHeader",
        "rows": [
            "a","b","c"
        ]
    },
    {
        "dataId": "2",
        "tableName": "table1",
        "column": "secondHeader",
        "rows": [
            "d","e","f",
        ]
    },
    {
        "dataId": "3",
        "tableName": "table2",
        "column": "aNewFirstHeader",
        "rows": [
            1,2,3
        ]
    }
];

И это желаемый результат:

[
  {
    "tableName": "table1",
    "column": ["firstHeader", "secondHeader"],
    "rows": [["a","b","c"], ["d","e","f"]]
  },
  {
    "tableName": "table2",
    "column": ["aNewFirstHeader"],
    "rows": [[1,2,3]]
  }
]

Два объекта, которые имели одно и то же свойство tableName, теперь собрали данные своих строк и столбцов в один объект.

Они использовали .groupBy() от Lodash, который не дает желаемого результата, но что интересно, для элегантного выполнения этой задачи не требуется никакой библиотеки.

Мне нравятся такие вопросы, потому что их можно решить с помощью собственных методов итерации массива, таких как .map() и .reduce().

В этом конкретном случае мое решение действительно использует эти две функции. Сначала я применяю .map(), чтобы «очистить» данные. Свойство rows и свойство column должны быть типа Array вместо String, чтобы мы могли хранить в них накопленные данные, а свойство dataId удалено.

Затем я применяю .reduce() к этому новому набору данных. .reduce() получает два параметра: функцию обратного вызова и начальное значение. Функция обратного вызова имеет два аргумента: аккумулятор, который содержит «сокращенные» данные, и второй аргумент, представляющий элемент массива текущей итерации.

Для достижения вышеуказанного результата мы проверяем, существует ли в аккумуляторе элемент с таким же свойством tableName. Если да, получите индекс этого элемента и добавьте значения свойств rows и column текущего элемента к обнаруженному элементу в аккумуляторе.

Если такого элемента в аккумуляторе нет, просто добавьте в аккумулятор полный текущий элемент.

Чтобы найти в аккумуляторе индекс элемента с таким же tableName, я проделал весь «.forEach()-танец», чтобы его найти. Пока не увидел решение другого разработчика на StackOverflow. Было сказано использовать .findIndex() для получения индекса.

Святое дерьмо! Я совсем забыл, что у нас есть это сейчас! Это функция, выпущенная вместе с ES2015, как и .find().

Добавление findIndex() к моему решению сделало его действительно приятным. Вот он во всей своей чистоте и элегантности:

const newArr = data.map(value => ({
  tableName: value.tableName,
  column: [value.column],
  rows: [value.rows],
}))
.reduce((acc, cur) => {
  const index = acc.findIndex(el => el.tableName === cur.tableName);
  if (index === -1) {
    acc.push(cur);
    return acc;
  }
  acc[index].column.push(cur.column[0]);
  acc[index].rows.push(cur.rows[0]);
  return acc;
}, []);
console.log(newArr);

Выпуск ES2015 содержал так много полезных функций, что здорово заново открыть их в реальных случаях использования. Кстати, ES2017 только что вышел и тоже содержит несколько приятных фич.