Konwertuj tablicę obiektów na mapę skrótu, indeksowaną przez wartość atrybutu obiektu

305

Przypadek użycia

Przypadek użycia polega na przekształceniu tablicy obiektów w mapę skrótu na podstawie ciągu znaków lub funkcji przewidzianej do oceny i użycia jako klucza w mapie skrótu i ​​wartości jako samego obiektu. Częstym przypadkiem korzystania z tego jest konwertowanie tablicy obiektów na mapę obiektów mieszających.

Kod

Poniżej znajduje się niewielki fragment kodu JavaScript służący do konwersji tablicy obiektów na mapę skrótu, indeksowaną wartością atrybutu obiektu. Możesz zapewnić funkcję do dynamicznej oceny klucza mapy skrótu (czas działania). Mam nadzieję, że to pomoże komuś w przyszłości.

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

Można znaleźć sedno tutaj: Konwertuje tablicę obiektów na HashMap .

Naveen I
źródło
Możesz użyć JavaScript Map zamiast Object. Sprawdź stackoverflow.com/a/54246603/5042169
Jun711

Odpowiedzi:

471

Jest to dość trywialne w przypadku 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' }

Uwaga: Array.prototype.reduce() jest IE9 +, więc jeśli potrzebujesz obsługi starszych przeglądarek, musisz go wypełnić.

jmar777
źródło
47
result = arr.reduce((map, obj) => (map[obj.key] = obj.val, map), {});Dla fanów One-Liner ES6: D
Teodor Sandu
31
@Mtz Dla fanów ES6 jedna linia, za odpowiedź mateuscb poniżej jest znacznie mniejsze i czystsze: result = new Map(arr.map(obj => [obj.key, obj.val]));. Co najważniejsze, jest bardzo jasne, że mapa jest zwracana.
Ryan Shillington,
2
@RyanShillington jesteśmy tutaj w kontekście odpowiedzi, która jest Array.prototype.reducezaproponowana przez jmar777. Mapjest rzeczywiście krótszy, ale to inna sprawa. Trzymałem się pierwotnego celu. Pamiętaj, że to nie jest forum, możesz przeczytać więcej o strukturze SO Q / A.
Teodor Sandu,
2
@Mtz Wystarczająco.
Ryan Shillington,
1
Nie o to proszono, IMHO. Poprawną do tablicy przedstawionej będzie: { "foo": {key: 'foo', val: 'bar'}, "hello": {key: 'hello', val: 'world'} }. Pamiętaj, że każdy oryginalny element powinien być zachowany w całości . Lub z wykorzystaniem danych Q za: {"345": {id:345, name:"kumar"}, ...}. POPRAWKA: Zmień kod namap[obj.key] = obj;
ToolmakerSteve
302

Korzystając z mapy ES6 ( całkiem dobrze obsługiwanej ), możesz spróbować:

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
źródło
4
Ważne jest również, aby pamiętać, że aby uzyskać coś, Mapmusisz użyć, result.get(keyName)a nie tylko result[keyName]. Należy również pamiętać, że dowolny obiekt może być używany jako klucz, a nie tylko ciąg znaków.
Simon_Weaver
5
Inna wersja TypeScript wyglądałaby tak: dla var result = new Map(arr.map(i => [i.key, i.val] as [string, string]));niektórych może być łatwiejsza do zrozumienia. as [string, string]Dodano rzutowanie typu notatki .
AlexV
Kiedy uruchamiam ten kod w Chrome v71, wciąż otrzymuję tablicę:Result is: [["foo","bar"],["hello","world"]]
Jean-François Beauchamp
PS resultnie jest hashem, jak wymaga OP.
Jean-François Beauchamp
1
Inna wersja maszynopisu:var result = new Map<string, string>(arr.map(i => [i.key, i.val]));
Aziz Javed
39

W przypadku lodash można to zrobić za pomocą 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}
drzazga
źródło
To nie jest skrót
Pomme De Terre
37

Za pomocą 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}
Shuk
źródło
2
Idealnie, właśnie tego potrzebowałem;)
Pierre
1
const hash = Object.assign({}, ...(<{}>array.map(s => ({[s.key]: s.value}))));musiałem dokonać tej zmiany, aby pracować z maszynopisem.
ruwan800
24

Za pomocą operatora rozprzestrzeniania:

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

Demonstracja fragmentu kodu w jsFiddle .

Pedro Lopes
źródło
7
Właśnie tu jestem z tego powodu! w jaki sposób operator rozprzestrzeniania działa ponownie w zwykły stary sposób, po prostu przypisując nowy klucz i zwracając akumulator? ponieważ za każdym razem tworzy nową kopię, rozkładanie będzie działało słabo !
AMTourky
1
teraz rozprzestrzeniasz się w każdej iteracji. Mutowanie w reduktorze powinno być bezpieczne. `` `const result = arr.reduce ((accumulator, target) => {accumulator [target.key]: target.val; return accumulator}, {}); ``
MTJ
17

Możesz użyć Array.prototype.reduce () i rzeczywistej mapy JavaScript zamiast tylko obiektu 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);

Czym różni się mapa od obiektu?
Wcześniej, zanim mapa została zaimplementowana w JavaScript, obiekt był używany jako mapa ze względu na podobną strukturę.
W zależności od przypadku użycia, jeśli potrzebujesz mieć zamówione klucze, musisz uzyskać dostęp do rozmiaru mapy lub często dodawać i usuwać mapę, preferowana jest mapa.

Cytat z dokumentu MDN :
Obiekty są podobne do Map, ponieważ oba umożliwiają ustawianie kluczy na wartości, pobieranie tych wartości, usuwanie kluczy i wykrywanie, czy coś jest przechowywane pod kluczem. Z tego powodu (i ponieważ nie było wbudowanych alternatyw) Obiekty były historycznie używane jako Mapy; istnieją jednak ważne różnice, które sprawiają, że korzystanie z mapy jest lepsze w niektórych przypadkach:

  • Kluczami obiektu są ciągi znaków i symbole, natomiast mogą mieć dowolną wartość dla mapy, w tym funkcje, obiekty i dowolne elementy pierwotne.
  • Klucze w Mapie są uporządkowane, a klucze dodane do obiektu nie są. Zatem podczas iteracji nad nim obiekt Map zwraca klucze w kolejności wstawiania.
  • Rozmiar mapy można łatwo uzyskać za pomocą właściwości size, a liczbę właściwości w obiekcie należy ustalić ręcznie.
  • Mapa jest iterowalna i dlatego może być bezpośrednio iterowana, podczas gdy iteracja nad Obiektem wymaga uzyskania kluczy w pewien sposób i iteracji nad nimi.
  • Obiekt ma prototyp, więc na mapie znajdują się domyślne klucze, które mogą kolidować z kluczami, jeśli nie będziesz ostrożny. Od wersji ES5 można to obejść, używając map = Object.create (null), ale rzadko tak się dzieje.
  • Mapa może działać lepiej w scenariuszach obejmujących częste dodawanie i usuwanie kluczowych par.
7 czerwca
źródło
1
Brakuje ci strzały. Zmień (mapAccumulator, obj) {...}na(mapAccumulator, obj) => {...}
Mayid
15

Możesz użyć nowej Object.fromEntries()metody.

Przykład:

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}

Fabiano Taioli
źródło
12

wersja es2015:

const myMap = new Map(objArray.map(obj => [ obj.key, obj.val ]));
baryo
źródło
4

To, co robię w TypeScript, mam małą bibliotekę utils, w której umieszczam takie rzeczy

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

stosowanie:

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

lub jeśli masz identyfikator inny niż „id”

const hash = arrayToHash([{key:1,data:'data'},{key:2,data:'data'}], 'key')
Piotr
źródło
Jeśli chcesz użyć obiektu jako klucza, musisz użyć mapy zamiast obiektu, ponieważ maszynopis nie pozwoli ci używać obiektów jako kluczy
Dany Dhondt
3

Są lepsze sposoby na to, jak wyjaśniono w innych plakatach. Ale jeśli chcę trzymać się czystego stylu JS i staromodnego, oto:

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
źródło
zaleca się stosowanie metody redukcji niż tej metody. Mam ochotę skorzystać z tej metody. wszystko jest proste i łatwe do zobaczenia.
Santhosh Yedidi
Uwielbiam to podejście. Myślę, że czasami najprostszy kod jest najlepszy. Ludzie są obecnie wyłączeni przez zmienność, ale dopóki jest zawarta, zmienność jest naprawdę niesamowita i wydajna.
Luis Aceituno
3

Jeśli chcesz przekonwertować na nową mapę ES6, wykonaj następujące czynności:

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

Dlaczego warto korzystać z tego typu mapy? Cóż, to zależy od ciebie. Spójrz na to .

Tiago Bértolo
źródło
2

Korzystanie z prostego 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
Partha Roy
źródło
3
nie używa .map (...) w tym przykładzie bez sensu, ponieważ nic w nim nie zwracasz? Sugerowałbym dla każdego w tym przypadku.
cuddlecheek
2

Z 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' }
Chociaż
źródło
1

Niewielka poprawa reduceużytkowania:

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' }
Mor Shemesh
źródło
Czy to jest szybsze niż druga odpowiedź?
orad
@orad prawdopodobnie nie, ponieważ rozkłada akumulator i tworzy nowe obiekty przy każdej iteracji.
Luckylooke
1

próbować

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

Kamil Kiełczewski
źródło
0

Poniżej znajduje się mały fragment, który utworzyłem w javascript, aby przekonwertować tablicę obiektów na mapę skrótu, indeksowaną według wartości atrybutu obiektu. Możesz zapewnić funkcję do dynamicznej oceny klucza mapy skrótu (czas działania).

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

Możesz znaleźć sedno tutaj: https://gist.github.com/naveen-ithappu/c7cd5026f6002131c1fa

Naveen I
źródło
11
Proszę, proszę, nie zalecamy przedłużania Array.prototype!
jmar777
Ahh rozumiem. Początkowo myślałem, że to proponowana odpowiedź :)
jmar777