Превратите массив в глубоко вложенный объект с помощью этой простой рекурсивной функции.

Потому что правила рекурсии.

Очень распространенный шаблон при приеме данных из API включает индексацию этих данных в вашем приложении, чтобы вы могли прочитать их без поиска.

Пример

Допустим, у вас есть API, который возвращает список элементов, например.

const ITEMS = [
  {
    title: 'The One',
    contract: '1',
    author: 'alice'
  },
  {
    title: 'It Takes Two',
    contract: '2',
    author: 'alice'
  },
  {
    title: '3x a lady',
    contract: '1',
    author: 'beck'
  },
  {
    title: 'Four on the Floor',
    contract: '1',
    author: 'candice'
  },
  {
    title: "In de'Pipe Going Five by Five",
    contract: '3',
    author: 'candice'
  }
]

В своем приложении вы обнаружите, что вам постоянно нужно искать конкретный item по его author и contract. Наивный подход был бы чем-то вроде

const getItemBy = (author, contract)
  => ITEMS.find(
    item => author === item.author && contract === item.contract
  )

Это нормально, если вы будете искать item нечасто. Но если вам нужно делать это все время, он начинает работать немного медленнее, постоянно ища ITEMS.

Вместо этого, что, если бы вы могли написать такую ​​функцию, как

const getItemBy = (author, contract) => ITEMS_INDEX[author][contract]

или более безопасно

const getItemBy = (author, contract )
  => ITEMS_INDEX[author] ? ITEMS_INDEX[author][contract] : undefined

В этом случае ITEMS_INDEX потребуется структура вроде

const ITEMS_INDEX = {
  alice: {
    '1': {
      title: 'The One',
      contract: '1',
      author: 'alice'
    },
    '2': {
      title: 'It Takes Two',
      contract: '2',
      author: 'alice'
    },
  },
  beck: {
    '1': {
      title: '3x a lady',
      contract: '1',
      author: 'beck'
    },
  },
  candice: {
    '1': {
      title: 'Four on the Floor',
      contract: '1',
      author: 'candice'
    },
    '3': {
      title: "In de'Pipe Going Five by Five",
      contract: '3',
      author: 'candice'
    }
  }
}

Создание индекса

Чтобы гибко построить ITEMS_INDEX из ITEMS, выexplode ITEMS.

const ITEMS_ARRAY = explode(ITEMS, 'author', 'contract')

Один ключ

Взломать массив элементов одним ключом легко.

const explodeOne = (array, key)
  => array.reduce((acc, item) => ({
    ...acc,
    [item[key]]: item
  }), {})

Таким образом, explodeOne(array, 'author') даст вам объект, элементы которого помечены их автором, но в этом примере каждый автор может иметь несколько элементов, различающихся contract элемента.

Много ключей

Если вам нужно несколько ключей, как в нашем примере выше, вам нужно использовать рекурсию.

const explode = (array, key, ...rest)
  => array.reduce((acc, item) => ({
    ...acc,
    [item[key]]: rest.length ? {
      ...acc[item[key]],
      ...explode([item], ...rest)
    } : item
  }), {})

Что происходит?

Здесь используется ряд удобных функций Javascript.

  1. Остаточный синтаксис для обработки произвольного количества ключей (array, key, ...rest),
  2. Редуктор массива, который просматривает массив и создает наш последний объект,
  3. Вычисляемые свойства [item[key]: ... означает, что вы можете ссылаться на или создавать свойство в объекте, не зная об этом заранее,
  4. Обозначение распространения объекта { ...acc, ...explode([item], ...rest) }, и
  5. Рекурсия, т.е. когда мы снова вызываем explode в функции explode для обработки следующего ключа для конкретного item.

Построчное объяснение

const explode = (array, key, ...rest) определяет функцию explode как принимающую аргументы array вместе с key и, необязательно, массивом других ключей, которые мы будем называть rest. Если ключей больше нет, то rest = [].

Эта функция вернет array, уменьшенное до object. Массив reducer возвращает постоянно расширяющийся объект acc (сокращенно от аккумулятора), который на каждом проходе добавляет ...acc, а именно то, что уже есть в объекте, плюс новый ключ [item[key]] с некоторыми связанными данными. В нашем примере, если item - это { title: 'The One', contract: '1', author: 'alice', data: 'one' }, а key - «автор», тогда { [item[key]]: ... } будет { alice: ... }.

Поскольку Алиса может быть автором нескольких элементов с разными контрактами, если есть больше ключей (например, rest.length !== 0), нам нужно убедиться, что объект в [item[key]] включает все, что уже было в acc[item[key]] ранее, а затем мы хотим explode только один item, используя rest ключей.

Если в rest больше нет ключей, мы просто вставляем сам item.

Преимущества

Функция explode дает вам очень простой способ индексировать данные, предоставленные в формате, не оптимизированном для вашего использования. Многие API-интерфейсы возвращают массивы из key, value пар, и преобразование их в объект, связанный с помощью предоставленных ключей, может впоследствии сэкономить вам много усилий.

Улучшения: упражнение для читателя

Если у нас уже есть допустимые author и contract, тогда все, что нам действительно нужно сохранить в нашем индексе, - это заголовки.

const TITLES_INDEX = {
  alice: {
    '1': 'The One',
    '2': 'It Takes Two'
  },
  beck: {
    '1': '3x a lady'
  },
  candice: {
    '1': 'Four on the Floor',
    '3': "In de'Pipe Going Five by Five"
  }
}
const getTitle = (author, contract)
  => TITLES_INDEX[author]
    ? TITLES_INDEX[author][contract]
    : undefined

Что бы вы изменили в приведенной выше функции explode, чтобы сгенерировать TITLES_INDEX?

Разместите свой ответ в ответах.

Ссылки