1

I have existing array and would like to add another object array to each item where the IDs match.

var existingData = [
    {
        "id": "0100",
        "name": "name 1",
        "message": [
            "Lorem blah blah 1",
            "Lorem blah blah 1.1"
        ]
    },
    {
        "id": "0200",
        "name": "name 2",
        "message": [
            "Lorem blah blah 2",
            "Lorem blah blah 2.1",
            "Lorem blah blah 2.2"
        ]
    },
    {
        "id": "0300",
        "name": "name 3",
        "message": [
            "Lorem blah blah 3",
            "Lorem blah blah 3.1",
            "Lorem blah blah 3.2",
            "Lorem blah blah 3.3",
            "Lorem blah blah 3.4"
        ]
    }
];

and the following array is my second array in which the "relatedId" will be the same as the IDs in the existingArray:

var data2 = [
  {"CandidateName": "Mary", "relatedId": ["0100", "0200"]},
  { "CandidateName": "John", "relatedId": ["0200"]},
  { "CandidateName":"Peter", "relatedId": ["0300", "0100"]},
  { "CandidateName": "Paul", "relatedId": ["0300"]}
];

If IDs match, I want to pull the "CandidateName" from data2 and put it into the existingData.

This particular question is has been extended from here => Group Javascript array of objects by ID

I have already made an attempt but I am not getting very far as the browser hangs:

var result = data.reduce(function(r, el) {

  // THIS INNER LOOP MAKES THE BROWSER HANG!!!
  data2.forEach(function (a){
    console.log('a',a); 
  });

  var e = el.id.slice(0, 2);
  if (!o[e]) {
    o[e] = {
      id: el.id,
      name: el.name,
      message: []
    }
    r.push(o[e]);
  }
  o[e].message.push(el.message);
  return r;
}, [])

So I want to end up with something like this:

var existingData = [
    {
        "id": "0100",
        "name": "name 1",
        "message": [
            "Lorem blah blah 1",
            "Lorem blah blah 1.1"
        ],
        "CandidateName": ["Mary", "Peter"]
    },
    {
        "id": "0200",
        "name": "name 2",
        "message": [
            "Lorem blah blah 2",
            "Lorem blah blah 2.1",
            "Lorem blah blah 2.2"
        ],
        "CandidateName": ["Mary", "John"]
    },
    {
        "id": "0300",
        "name": "name 3",
        "message": [
            "Lorem blah blah 3",
            "Lorem blah blah 3.1",
            "Lorem blah blah 3.2",
            "Lorem blah blah 3.3",
            "Lorem blah blah 3.4"
        ],
        "CandidateName": ["Peter", "Paul"]
    }
]
Community
  • 1
  • 1
Mr. Benedict
  • 809
  • 2
  • 10
  • 15
  • It appears that your situation might be simplified by having `existingData` be an Object that contains Objects instead of an array of Objects. The keys for `existingData` could then be the `id` of each Object. This would allow you to have direct access instead of having to go searching through the array each time you want the object that corresponds to the `id`. This is, more or less, the exact type of situation for which Objects exists. – Makyen Sep 26 '16 at 18:36

4 Answers4

3

It's far more efficient to create a hash map from data2 so you only ever iterate that array once. The keys would be the id's

Then while you iterate existingData it is a simple merge

var candidatesById = data2.reduce(function(a, c){
   c.relatedId.forEach(function(id){
       a[id] = a[id] || [];
       a[id].push(c.CandidateName);           
   });
   return a;
},{});

existingData.forEach(function(item){
    item.CandidateName = candidatesById[item.id] || [];// empty array if no match
})
charlietfl
  • 170,828
  • 13
  • 121
  • 150
2

Try this! The concept being, use the initial data, loop through and search for IDs, add applicable data.

Do note that this isn't a particularly efficient solution.

var existingData = [{
  "id": "0100",
  "name": "name 1",
  "message": [
    "Lorem blah blah 1",
    "Lorem blah blah 1.1"
  ]
}, {
  "id": "0200",
  "name": "name 2",
  "message": [
    "Lorem blah blah 2",
    "Lorem blah blah 2.1",
    "Lorem blah blah 2.2"
  ]
}, {
  "id": "0300",
  "name": "name 3",
  "message": [
    "Lorem blah blah 3",
    "Lorem blah blah 3.1",
    "Lorem blah blah 3.2",
    "Lorem blah blah 3.3",
    "Lorem blah blah 3.4"
  ]
}];

var toCombine = [{
  "CandidateName": "Mary",
  "relatedId": ["0100", "0200"]
}, {
  "CandidateName": "John",
  "relatedId": ["0200"]
}, {
  "CandidateName": "Peter",
  "relatedId": ["0300", "0100"]
}, {
  "CandidateName": "Paul",
  "relatedId": ["0300"]
}];

function merge() {
  existingData.forEach(function(initialItem) {
    toCombine.forEach(function(referenceItem) {
      if (referenceItem.relatedId.indexOf(initialItem.id) >= 0) {
        if (!Array.isArray(initialItem.CandidateName)) {
          initialItem.CandidateName = [];
        }
        initialItem.CandidateName.push(referenceItem.CandidateName);
      }
    });
  });
  console.log(existingData);
}

merge();
acupofjose
  • 2,159
  • 1
  • 22
  • 40
  • 1
    Score! It works. Well done. I'm going to have to take a leaf out of your book(s). Literally – Mr. Benedict Sep 26 '16 at 18:48
  • It appears as though it would be more efficient to iterate through the related data array rather than the existing data based on the example given. There is no guarantee that there will be a related record for every existing record which will result in unnecessary iterations. Since you definitely have to search through the existing array for each related item to check their IDs, you may as well only loop through them once. Your code looks good otherwise. I would personally switch to an object instead of array as mentioned above if possible. – Dan Knox Sep 26 '16 at 18:51
  • I know I went about it the wrong way to start with. – Mr. Benedict Sep 26 '16 at 19:09
1

Here is another ES6 solution:

data2.forEach( function (obj) {
    obj.relatedId.forEach( id => this.get(id).candidateName.push(obj.CandidateName));
}, new Map(existingData.map (
    obj => [obj.id, Object.assign(obj, { 'candidateName': [] } )] 
)));

var existingData = [
    {
        "id": "0100",
        "name": "name 1",
        "message": [
            "Lorem blah blah 1",
            "Lorem blah blah 1.1"
        ]
    },
    {
        "id": "0200",
        "name": "name 2",
        "message": [
            "Lorem blah blah 2",
            "Lorem blah blah 2.1",
            "Lorem blah blah 2.2"
        ]
    },
    {
        "id": "0300",
        "name": "name 3",
        "message": [
            "Lorem blah blah 3",
            "Lorem blah blah 3.1",
            "Lorem blah blah 3.2",
            "Lorem blah blah 3.3",
            "Lorem blah blah 3.4"
        ]
    }
];

var data2 = [
  {"CandidateName": "Mary", "relatedId": ["0100", "0200"]},
  { "CandidateName": "John", "relatedId": ["0200"]},
  { "CandidateName":"Peter", "relatedId": ["0300", "0100"]},
  { "CandidateName": "Paul", "relatedId": ["0300"]}
];

data2.forEach( function (obj) {
    obj.relatedId.forEach( id => this.get(id).candidateName.push(obj.CandidateName));
}, new Map(existingData.map (
    obj => [obj.id, Object.assign(obj, { 'candidateName': [] } )] 
)));

console.log(existingData);

This turns the existingData into a map, keyed by id, while adding the candidateName array (empty) to the objects.

This map is passed as the second argument to forEach, thus defining it as the this object.

Inside the forEach on data2 elements, the relatedId values are iterated and for each of these id values the corresponding existingData object's candidateName array is extended with the CandidateName.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Shortest lines of code and ultra fast. Must try to implment this. ES6 is something I've recently started looking at so must use this one. Thanks trincot – Mr. Benedict Sep 26 '16 at 19:06
1

May be we can do something like this in ES6 too; Well.. i create a hash table from data2 and use it as a this object in map.

var existingData = [
    {
        "id": "0100",
        "name": "name 1",
        "message": [
            "Lorem blah blah 1",
            "Lorem blah blah 1.1"
        ]
    },
    {
        "id": "0200",
        "name": "name 2",
        "message": [
            "Lorem blah blah 2",
            "Lorem blah blah 2.1",
            "Lorem blah blah 2.2"
        ]
    },
    {
        "id": "0300",
        "name": "name 3",
        "message": [
            "Lorem blah blah 3",
            "Lorem blah blah 3.1",
            "Lorem blah blah 3.2",
            "Lorem blah blah 3.3",
            "Lorem blah blah 3.4"
        ]
    }
],
 data2 = [
  {"CandidateName": "Mary", "relatedId": ["0100", "0200"]},
  { "CandidateName": "John", "relatedId": ["0200"]},
  { "CandidateName":"Peter", "relatedId": ["0300", "0100"]},
  { "CandidateName": "Paul", "relatedId": ["0300"]}
],

newExistingData = existingData.map(function(o){
                                     o.CandidateName = this[o.id];
                                     return o;
                                   }, data2.reduce((h,o) => (o.relatedId.reduce((p,c) => (p[c] ? p[c].push(o.CandidateName)
                                                                                        : p[c] = [o.CandidateName],p),h),h),{}));
console.log(newExistingData);
Redu
  • 25,060
  • 6
  • 56
  • 76
  • Quite keen on ES^ implementation of this but I'm getting a "TypeError: Cannot read property 'reduce' of undefined" around the `=> (o.relatedId.reduce(` block of code on the third line of your code. Any ideas of how to check for 'undefined' type? – Mr. Benedict Sep 26 '16 at 20:26
  • @Mr. Benedict Wow.. I checked it with FF v50 and Chrome v54.. and running fine. What browser are you using? – Redu Sep 26 '16 at 20:37
  • I'm actually using Chrome but I think the issue is with the real data set which is more extensive. There will be instances where there will be no `relatedId`s so when you 'reduce', I guess it will not know what to do next so it come up as undefined. How do you overcome such scenarios? – Mr. Benedict Sep 26 '16 at 20:48
  • @Mr. Benedict Now i see.. This code expects the `relatedId` to be at least `[]` In case there is no such property then there is always `data2.reduce((h,o) => o.relatedId ? (o.relatedId.reduce((p,c) => ... : h` option. – Redu Sep 26 '16 at 20:55