Перетворити масив об'єктів у хеш-карту, індексовану значенням атрибута Об'єкта


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
Jun711

Відповіді:


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 +, тому якщо вам потрібно підтримувати старіші веб-переглядачі, вам потрібно буде заповнити його.


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
@Mtz Справедливо.
Райан Шиллінгтон

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"}


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"]]
Jean-François Beauchamp

PS result- це не хеш, як вимагає ОП.
Жан-Франсуа

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}

Це не хешмап
Pomme De Terre

37

Використання ES6 спред + 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 Map, а не просто Об'єкт 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 повертає ключі в порядку вставки.
  • Ви можете легко отримати розмір карти за допомогою властивості розміру, при цьому кількість властивостей об’єкта потрібно визначити вручну.
  • Карта є ітерабельною і тому може бути безпосередньо повторена, тоді як ітерація над Об'єктом вимагає отримання його ключів певним чином та повторення над ними.
  • Об'єкт має прототип, тому на карті є ключі за замовчуванням, які можуть зіткнутися з вашими ключами, якщо ви не будете обережні. Станом на ES5 це можна обійти за допомогою map = Object.create (null), але це робиться рідко.
  • Карта може працювати краще в сценаріях, що передбачають часте додавання та видалення пар ключів.

1
Вам не вистачає стрілка. Зміна (mapAccumulator, obj) {...}на(mapAccumulator, obj) => {...}
травень



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, оскільки typecript не дозволить вам використовувати об’єкти як ключі
Dany

3

Є кращі способи зробити це, як пояснено іншими плакатами. Але якщо я хочу дотримуватися чистого модного JS та ol 'способу, то ось це:

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);

чи рекомендується використовувати метод зменшення, ніж цей метод. Мені здається, що я використовую цей метод. його просто і легко бачити все.
Сантош Єдіді

Я люблю такий підхід. Я думаю, що іноді найпростіший код - найкращий. Сьогодні люди відключені через незмінність, але поки вона міститься, незмінність насправді є приголомшливою та ефективною.
Луїс

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 (...) безглуздо в цьому прикладі, оскільки ви нічого не повертаєте в ньому? Я б запропонував для кожного в цьому випадку.
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' }

Чи швидше, ніж інша відповідь?
орад

@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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.