Преобразовать массив объектов в хэш-карту, проиндексированную по значению атрибута объекта

305

Случай использования

Вариант использования - преобразование массива объектов в хеш-карту на основе строки или функции, предоставленной для оценки и использования в качестве ключа в хеш-карте и значения в качестве самого объекта. Распространенным случаем использования этого является преобразование массива объектов в хэш-карту объектов.

Код

Ниже приведен небольшой фрагмент в JavaScript для преобразования массива объектов в хэш-карту, проиндексированную по значению атрибута объекта. Вы можете предоставить функцию для динамической оценки ключа хэш-карты (время выполнения). Надеюсь, это поможет кому-то в будущем.

function isFunction(func) {
    return Object.prototype.toString.call(func) === '[object Function]';
}

/**
 * This function converts an array to hash map
 * @param {String | function} key describes the key to be evaluated in each object to use as key for hashmap
 * @returns Object
 * @Example 
 *      [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap("id")
 *      Returns :- Object {123: Object, 345: Object}
 *
 *      [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap(function(obj){return obj.id+1})
 *      Returns :- Object {124: Object, 346: Object}
 */
Array.prototype.toHashMap = function(key) {
    var _hashMap = {}, getKey = isFunction(key)?key: function(_obj){return _obj[key];};
    this.forEach(function (obj){
        _hashMap[getKey(obj)] = obj;
    });
    return _hashMap;
};

Вы можете найти суть здесь: Преобразует массив объектов в HashMap .

Навин я
источник
Вы можете использовать JavaScript Map вместо Object. Проверьте stackoverflow.com/a/54246603/5042169
Jun 711

Ответы:

471

Это довольно тривиально, чтобы сделать с Array.prototype.reduce:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = arr.reduce(function(map, obj) {
    map[obj.key] = obj.val;
    return map;
}, {});

console.log(result);
// { foo:'bar', hello:'world' }

Примечание: Array.prototype.reduce() это IE9 +, поэтому, если вам нужно поддерживать старые браузеры, вам нужно его заполнить.

jmar777
источник
47
result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});Для фанатов ES6 с одним вкладышем: D
Теодор Санду
31
@Mtz Для ES6 вентиляторов однолинейные, ответ mateuscb в ниже намного меньше и чище: result = new Map(arr.map(obj => [obj.key, obj.val]));. Что наиболее важно, это очень ясно дает понять, что карта возвращается.
Райан Шиллингтон
2
@RyanShillington, мы находимся здесь в контексте ответа, который Array.prototype.reduceпредложен jmar777. Mapдействительно короче, но это совсем другое. Я придерживался первоначального намерения. Помните, что это не форум, вы можете прочитать больше о структуре SO Q / A.
Теодор Санду
2
@ Мц Достаточно справедливо.
Райан Шиллингтон
1
Это не то, о чем просили, ИМХО. Правильный результат для массива , показанного будет: { "foo": {key: 'foo', val: 'bar'}, "hello": {key: 'hello', val: 'world'} }. Обратите внимание, что каждый оригинальный элемент должен быть сохранен целиком . Или , используя данные добротность в: {"345": {id:345, name:"kumar"}, ...}. FIX: Изменить код будетmap[obj.key] = obj;
ToolmakerSteve
302

Используя ES6 Map ( довольно хорошо поддерживается ), вы можете попробовать это:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = new Map(arr.map(i => [i.key, i.val]));

// When using TypeScript, need to specify type:
// var result = arr.map((i): [string, string] => [i.key, i.val])

// Unfortunately maps don't stringify well.  This is the contents in array form.
console.log("Result is: " + JSON.stringify([...result])); 
// Map {"foo" => "bar", "hello" => "world"}

mateuscb
источник
4
Также важно отметить, что для получения чего- Mapто нужно использовать, result.get(keyName)а не просто result[keyName]. Также обратите внимание, что любой объект может быть использован в качестве ключа, а не просто строка.
Simon_Weaver
5
Другая версия TypeScript выглядела бы так: var result = new Map(arr.map(i => [i.key, i.val] as [string, string]));некоторые могут найти ее более понятной. as [string, string]Добавлен тип заметки .
AlexV
Когда я запускаю этот код в Chrome v71, я все еще получаю массив:Result is: [["foo","bar"],["hello","world"]]
Жан-Франсуа Бошан
PS resultне является хешем, как того требует OP.
Жан-Франсуа Бошан
1
Другая машинописная версия:var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
Азиз Джавед
39

С помощью lodash это можно сделать с помощью keyBy :

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = _.keyBy(arr, o => o.key);

console.log(result);
// Object {foo: Object, hello: Object}
splintor
источник
Это не хэш-карта
Pomme De Terre
37

Использование ES6 spread + Object.assign:

array = [{key: 'a', value: 'b', redundant: 'aaa'}, {key: 'x', value: 'y', redundant: 'zzz'}]

const hash = Object.assign({}, ...array.map(s => ({[s.key]: s.value})));

console.log(hash) // {a: b, x: y}
Шук
источник
2
Отлично, именно то, что мне было нужно;)
Пьер
1
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value}))));пришлось сделать это изменение для работы с машинописью.
ruwan800
24

Используя оператор распространения:

const result = arr.reduce(
    (accumulator, target) => ({ ...accumulator, [target.key]: target.val }),
    {});

Демонстрация фрагмента кода на jsFiddle .

Педро Лопес
источник
7
Я именно здесь из-за этого! как оператор спреда выполняет обычный старый способ - просто назначить новый ключ и вернуть аккумулятор? поскольку он каждый раз создает новую копию, распространение будет работать плохо !
AMTourky
1
Теперь вы распространяетесь в каждой итерации. Должно быть безопасно мутировать в редукторе. `` `const result = arr.reduce ((аккумулятор, цель) => {аккумулятор [target.key]: target.val; вернуть аккумулятор}, {}); `` `
MTJ
17

Вы можете использовать Array.prototype.reduce () и реальную карту JavaScript вместо просто объекта JavaScript .

let keyValueObjArray = [
  { key: 'key1', val: 'val1' },
  { key: 'key2', val: 'val2' },
  { key: 'key3', val: 'val3' }
];

let keyValueMap = keyValueObjArray.reduce((mapAccumulator, obj) => {
  // either one of the following syntax works
  // mapAccumulator[obj.key] = obj.val;
  mapAccumulator.set(obj.key, obj.val);

  return mapAccumulator;
}, new Map());

console.log(keyValueMap);
console.log(keyValueMap.size);

Что отличается между картой и объектом?
Ранее, до того как Map была реализована в JavaScript, Object использовался в качестве Map из-за их схожей структуры.
В зависимости от вашего случая использования, если вам нужны заказанные ключи, вам нужен доступ к размеру карты или частое добавление и удаление с карты, карта предпочтительна.

Цитата из документа MDN :
объекты похожи на Карты в том, что оба позволяют устанавливать ключи к значениям, извлекать эти значения, удалять ключи и определять, хранится ли что-то в ключе. Из-за этого (и потому что не было никаких встроенных альтернатив), Объекты исторически использовались как Карты; однако, есть важные отличия, которые делают использование карты предпочтительным в определенных случаях:

  • Ключами объекта являются строки и символы, в то время как они могут иметь любое значение для карты, включая функции, объекты и любые примитивы.
  • Ключи в Map упорядочены, а ключи, добавленные к объекту - нет. Таким образом, при итерации по нему объект Map возвращает ключи в порядке вставки.
  • Вы можете легко получить размер карты с помощью свойства size, в то время как количество свойств в Object должно быть определено вручную.
  • Карта является итеративной и, таким образом, может быть непосредственно повторена, тогда как для итерации по объекту требуется получить его ключи некоторым образом и итерировать по ним.
  • У объекта есть прототип, поэтому на карте есть ключи по умолчанию, которые могут столкнуться с вашими ключами, если вы не будете осторожны. Начиная с ES5 это можно обойти, используя map = Object.create (null), но это редко делается.
  • Карта может работать лучше в сценариях, включающих частое добавление и удаление пар ключей.
Jun711
источник
1
Вам не хватает стрелки. Изменение (mapAccumulator, obj) {...}для(mapAccumulator, obj) => {...}
Mayid
15

Вы можете использовать новый Object.fromEntries()метод.

Пример:

const array = [
   {key: 'a', value: 'b', redundant: 'aaa'},
   {key: 'x', value: 'y', redundant: 'zzz'}
]

const hash = Object.fromEntries(
   array.map(e => [e.key, e.value])
)

console.log(hash) // {a: b, x: y}

Фабиано Тайоли
источник
12

Версия es2015:

const myMap = new Map(objArray.map(obj => [ obj.key, obj.val ]));
baryo
источник
4

Это то, что я делаю в TypeScript. У меня есть небольшая библиотека утилит, в которую я помещаю подобные вещи.

export const arrayToHash = (array: any[], id: string = 'id') => 
         array.reduce((obj, item) =>  (obj[item[id]] = item , obj), {})

использование:

const hash = arrayToHash([{id:1,data:'data'},{id:2,data:'data'}])

или если у вас есть идентификатор, отличный от «id»

const hash = arrayToHash([{key:1,data:'data'},{key:2,data:'data'}], 'key')
Питер
источник
Если вы хотите использовать объект в качестве ключа, вам придется использовать Map вместо Object, потому что машинопись не позволит вам использовать объекты в качестве ключей
Dany Dhondt
3

Есть лучшие способы сделать это, как объяснено другими авторами. Но если я хочу придерживаться чистого JS и по-старому, то вот оно:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' },
    { key: 'hello', val: 'universe' }
];

var map = {};
for (var i = 0; i < arr.length; i++) {
    var key = arr[i].key;
    var value = arr[i].val;

    if (key in map) {
        map[key].push(value);
    } else {
        map[key] = [value];
    }
}

console.log(map);
rhel.user
источник
Рекомендуется использовать метод Reduce, чем этот метод. Мне хочется использовать этот метод. все просто и легко увидеть.
Сантош Йедиди
Мне нравится этот подход. Я думаю, что иногда самый простой код - лучший. В наше время люди отключаются благодаря изменчивости, но пока они содержатся, изменчивость на самом деле довольно удивительна и эффективна.
Луис Асейтуно
3

Если вы хотите перейти на новую карту ES6, сделайте это:

var kvArray = [['key1', 'value1'], ['key2', 'value2']];
var myMap = new Map(kvArray);

Почему вы должны использовать этот тип карты? Ну, это зависит от вас. Посмотрите на это .

Тиаго Бертоло
источник
2

Используя простой Javascript

var createMapFromList = function(objectList, property) {
    var objMap = {};
    objectList.forEach(function(obj) {
      objMap[obj[property]] = obj;
    });
    return objMap;
  };
// objectList - the array  ;  property - property as the key
Партха Рой
источник
3
не использует .map (...) бессмысленно в этом примере, поскольку вы ничего не возвращаете в нем? Я бы предложил forEach в этом случае.
cuddlecheek
2

С lodash:

const items = [
    { key: 'foo', value: 'bar' },
    { key: 'hello', value: 'world' }
];

const map = _.fromPairs(items.map(item => [item.key, item.value]));

console.log(map); // { foo: 'bar', hello: 'world' }
Тхо
источник
1

Небольшое улучшение в reduceиспользовании:

var arr = [
    { key: 'foo', val: 'bar' },
    { key: 'hello', val: 'world' }
];

var result = arr.reduce((map, obj) => ({
    ...map,
    [obj.key] = obj.val
}), {});

console.log(result);
// { foo: 'bar', hello: 'world' }
Мор Шемеш
источник
Это быстрее, чем другой ответ?
19
@orad, вероятно, не так, как он распространяет аккумулятор и создает новый объект каждую итерацию.
Luckylooke
1

пытаться

let toHashMap = (a,f) => a.reduce((a,c)=> (a[f(c)]=c,a),{});

Камил Келчевски
источник
0

Ниже приведен небольшой фрагмент, который я создал в javascript для преобразования массива объектов в хэш-карту, проиндексированную по значению атрибута объекта. Вы можете предоставить функцию для динамической оценки ключа хэш-карты (время выполнения).

function isFunction(func){
    return Object.prototype.toString.call(func) === '[object Function]';
}

/**
 * This function converts an array to hash map
 * @param {String | function} key describes the key to be evaluated in each object to use as key for hasmap
 * @returns Object
 * @Example 
 *      [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap("id")
        Returns :- Object {123: Object, 345: Object}

        [{id:123, name:'naveen'}, {id:345, name:"kumar"}].toHashMap(function(obj){return obj.id+1})
        Returns :- Object {124: Object, 346: Object}
 */
Array.prototype.toHashMap = function(key){
    var _hashMap = {}, getKey = isFunction(key)?key: function(_obj){return _obj[key];};
    this.forEach(function (obj){
        _hashMap[getKey(obj)] = obj;
    });
    return _hashMap;
};

Вы можете найти суть здесь: https://gist.github.com/naveen-ithappu/c7cd5026f6002131c1fa

Навин я
источник
11
Пожалуйста, пожалуйста, пожалуйста, не рекомендуйте расширение Array.prototype!
jmar777
АА, вижу. Я изначально думал, что это был предложенный ответ :)
jmar777