web-dev-qa-db-fra.com

JavaScript fusionnant des objets par identifiant

Quelle est la bonne façon de fusionner deux tableaux en Javascript?

J'ai deux tableaux (par exemple):

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]

Je veux pouvoir me retrouver avec quelque chose comme:

var a3 = [{ id : 1, name : "test", count : "1"}, 
          { id : 2, name : "test2", count : "2"}]

Lorsque les deux tableaux sont joints en fonction du champ 'id' et que des données supplémentaires sont simplement ajoutées. 

J'ai essayé d'utiliser _.union pour le faire, mais cela écrase simplement les valeurs du second tableau dans le premier.

21
Tam2

Cela devrait faire l'affaire:

var mergedList = _.map(a1, function(item){
    return _.extend(item, _.findWhere(a2, { id: item.id }));
});

Cela suppose que l'id du second objet dans a1 devrait être 2 plutôt que "2"

31
Gruff Bunny

En supposant que les identifiants sont des chaînes et que l'ordre n'a pas d'importance, vous pouvez 

  1. Créez une table de hachage.
  2. Itérez les deux tableaux et stockez les données dans la table de hachage, indexée par l'ID. Si certaines données portent déjà cet ID, mettez-le à jour avec Object.assign (ES6, peut être polyfilled ).
  3. Obtenez un tableau avec les valeurs de la carte de hachage.
var hash = Object.create(null);
a1.concat(a2).forEach(function(obj) {
    hash[obj.id] = Object.assign(hash[obj.id] || {}, obj);
});
var a3 = Object.keys(hash).map(function(key) {
    return hash[key];
});

Dans ECMAScript6, si les ID ne sont pas nécessairement des chaînes, vous pouvez utiliser Map :

var hash = new Map();
a1.concat(a2).forEach(function(obj) {
    hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj))
});
var a3 = Array.from(hash.values());
12
Oriol

réduire version.

var a3 = a1.concat(a2).reduce((acc, x) => {
    acc[x.id] = Object.assign(acc[x.id] || {}, x);
    return acc;
}, {});
_.values(a3);

Je pense que c'est une pratique courante dans le langage fonctionnel.

6
Daishi Nakajima

La mise en oeuvre de lodash:

var merged = _.map(a1, function(item) {
    return _.assign(item, _.find(a2, ['id', item.id]));
});

Le résultat:

[  
   {  
      "id":1,
      "name":"test",
      "count":"1"
   },
   {  
      "id":2,
      "name":"test2",
      "count":"2"
   }
]
4
bora89

ES6 simplifie ceci:

let merge = (obj1, obj2) => ({...obj1, ...obj2});

Notez que les clés répétées seront fusionnées et que la valeur du second objet prévaudra et la valeur répétée du premier objet sera ignorée .

Exemple:

let obj1 = {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj1Val"};
let obj2 = {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj2Val"};

merge(obj1, obj2)
// {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj2Val", uniqueObj2Key: "uniqueKeyValueObj2"}
merge(obj2, obj1)
// {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj1Val", uniqueObj1Key: "uniqueKeyValueObj1"}
4
Alberto

Il y a déjà beaucoup de bonnes réponses, je vais simplement en ajouter une autre, qui est un vrai problème que j'avais besoin de résoudre hier. 

J'avais un tableau de messages avec des identifiants d'utilisateur et un tableau d'utilisateurs contenant les noms des utilisateurs et d'autres détails. C’est ainsi que j’ai réussi à ajouter des détails d’utilisateur aux messages. 

var messages = [{userId: 2, content: "Salam"}, {userId: 5, content: "Hello"},{userId: 4, content: "Moi"}];
var users = [{id: 2, name: "Grace"}, {id: 4, name: "Janetta"},{id: 5, name: "Sara"}];

var messagesWithUserNames = messages.map((msg)=> {
  var haveEqualId = (user) => user.id === msg.userId
  var userWithEqualId= users.find(haveEqualId)
  return Object.assign({}, msg, userWithEqualId)
})
console.log(messagesWithUserNames)
2
Nick
const a3 = a1.map(it1 => {
   it1.test = a2.find(it2 => it2.id === it1.id).test
   return it1
 })
0
quy pham

Cela devrait faire l'affaire en es6 et c'est fonctionnel;

const a1 = [{ id: 1, name: 'test' }, { id: 2, name: 'test2' }];
const a2 = [{ id: 1, count: '1' }, { id: 2, count: '2' }];

const mergeArray = (source, merge, by) => source.map(item => ({
  ...item,
  ...(merge.find(i => i[by] === item[by]) || {}),
}));

console.log(mergeArray(a1, a2, 'id'));
// > ​​​​​[{ id: 1, name: 'test', count: '1' }, { id: 2, name: 'test2', count: '2' }]

Cela suppose également que l'id du deuxième objet de a1 devrait être 2 plutôt que "2".

0
Micah

Une version de travail TypeScript:

export default class Merge {
  static byKey(a1: any[], a2: any[], key: string) {
    const res = a1.concat(a2).reduce((acc, x) => {
      acc[x[key]] = Object.assign(acc[x[key]] || {}, x);
      return acc;
    }, {});

    return Object.entries(res).map(pair => {
      const [, value] = pair;
      return value;
    });
  }
}

test("Merge", async () => {
  const a1 = [{ id: "1", value: "1" }, { id: "2", value: "2" }];
  const a2 = [{ id: "2", value: "3" }];

  expect(Merge.byKey(a1, a2, "id")).toStrictEqual([
    {
      id: "1",
      value: "1"
    },
    { id: "2", value: "3" }
  ]);
});

0
Jeff Tian

Vous pouvez écrire une fonction de fusion d'objet simple comme celle-ci

function mergeObject(cake, icing) {
    var icedCake = {}, ingredient;
    for (ingredient in cake)
        icedCake[ingredient] = cake[ingredient];
    for (ingredient in icing)
        icedCake[ingredient] = icing[ingredient];
    return icedCake;
}

Ensuite, vous devez utiliser une double boucle pour l’appliquer à votre structure de données.

var i, j, a3 = a1.slice();
for (i = 0; i < a2.length; ++i)                // for each item in a2
    for (j = 0; i < a3.length; ++i)            // look at items in other array
        if (a2[i]['id'] === a3[j]['id'])       // if matching id
            a3[j] = mergeObject(a3[j], a2[i]); // merge

Vous pouvez également utiliser mergeObject comme clone simple en transmettant un paramètre sous forme d'objet vide.

0
Paul S.