1

Ok, another in the series of grouping Javascript arrays of objects by ID but this time, we have an array of IDs in an object of arrays (item3) which will be compared with another array of objects.

var existingArray = [
    {
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200","0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100","0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200", "0100"],
      "item4": "blah4",
      "item5": "blah5"
    }
]

Here's our favourite DATA2 array which contains the piece of information we want to pull out (CandidateName) if the "relatedId" is the same as the any of the IDs in item3 in EXISTINGARRAY.

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

So the idea is if any of the IDs in data2[i].relatedId[j] === existingArray[k].item3[l], pull out the "CandidateName" and add it to the EXISTINGARRAY so we end up with something like the following.

existingArray = [
    {
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200","0300"],
      "item4": "blah4",
      "item5": "blah5",
      "item6": ["Mary", "Jonh", "Peter", "Paul"]
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100","0300"],
      "item4": "blah4",
      "item5": "blah5",
      "item6": ["Mary", "Peter", "Paul"]
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100"],
      "item4": "blah4",
      "item5": "blah5",
      "item6": ["Mary", "Peter"]
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0300"],
      "item4": "blah4",
      "item5": "blah5",
      "item6": ["Peter", "Paul"]
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200", "0100"],
      "item4": "blah4",
      "item5": "blah5",
      "item6": ["Mary", "John","Peter"]
    }
]
Mr. Benedict
  • 809
  • 2
  • 10
  • 15
  • And your question is...? – Ridcully Sep 27 '16 at 20:03
  • Why doesn't first one gets attached with `"item6": ["Mary", "John", "Peter", "Paul"]` – Redu Sep 27 '16 at 20:12
  • How does this differ in essence from your [previous question](http://stackoverflow.com/questions/39709805/pull-items-from-one-array-and-add-to-an-existing-array-javascript)? And are you still planning to accept an answer there? – trincot Sep 27 '16 at 20:21
  • I have tried existingArray.reduce and within that, I did another data2.reduce but no joy. You're right @trincot, the intended outcome is essentially the same but the fact that I am looping through both arrays and within both, I am looping through two more arrays. Just got pretty messy really quickly. – Mr. Benedict Sep 27 '16 at 20:31
  • @Ridcully I want to pull CandidateName from data2 and put them into existingArray if the IDs in item3 inside existingArray matches relatedId within data2. See where it says: "so we end up with something like the following." – Mr. Benedict Sep 27 '16 at 20:35
  • I agree with Ridcully; John should be in item6 of the first object, since 200 is a link between John and item3 of that object. – trincot Sep 27 '16 at 20:39
  • Thanks guys. Just noticed that that "John" should be in the first set as well. I've added the name "John" to the first "item6". Thanks – Mr. Benedict Sep 27 '16 at 20:46

2 Answers2

1

Here is an ES6 solution for this:

existingArray.forEach( function (obj) {
    obj.item6 = [...new Set(obj.item3.reduce( (acc, id) => acc.concat(this.get(id)), [] ))]
}, data2.reduce (
    (acc, obj) => obj.relatedId.reduce (
        (acc, id) => acc.set(id, (acc.get(id) || []).concat(obj.CandidateName)), acc
    ), new Map()
));

var existingArray = [
    {
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200","0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100","0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200", "0100"],
      "item4": "blah4",
      "item5": "blah5"
    }
]

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

existingArray.forEach( function (obj) {
    obj.item6 = [...new Set(obj.item3.reduce( (acc, id) => acc.concat(this.get(id)), [] ))]
}, data2.reduce (
    (acc, obj) => obj.relatedId.reduce (
        (acc, id) => acc.set(id, (acc.get(id) || []).concat(obj.CandidateName)), acc
    ), new Map()
));

console.log(existingArray);

Explanation

The code really starts at the end, with the creation of an empty Map:

new Map()

This becomes the variable (named acc) that accumulates data while data2 is iterated with reduce:

data2.reduce

This reduce operation is nested in order to iterate each relatedId individually. If the accumulated value (acc) does not yet contain the found id, a new array is created:

acc.get(id) || []

... otherwise the found array value is used. To that the candidate name is appended:

.concat(obj.CandidateName)

... and this is put back into acc for key id:

acc.set(id, ...)

As the set method returns acc itself, it works very nicely with reduce which needs this return value in order to pass acc again to the next call of the callback.

The result of the outer reduce call is this a Map that is keyed by all id values found in data2, and the value for each is an array of associated names.

This value is then passed as second argument to the forEach call, and thus becomes the value of this. So when you see:

this.get(id)

it is retrieving the candidate name array for id from the Map described above.

In the forEach callback another reduce is made to iterate over the id values in item3:

obj.item3.reduce

This accumulates to an array of names which is then passed to the Set constructor:

new Set(...)

This is done to remove duplicates from the array of names. This Set is immediately converted to an array again with the spread operator:

[...new Set()]

And so all item6 properties get their value.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thanks for breaking this down Trincot. I just realised what you meant by "accepted answer" on the previous version question. I thought I had clicked the tick but I didn't. Works like a charm :) – Mr. Benedict Sep 27 '16 at 21:43
  • Professor @trincot, I'm still getting to grips with ES6, how to do check for "TypeError: Cannot read property 'reduce' of undefined". Normally, I would do an if statement and check if something exists or check for length greater than zero or something. How do I do this with your very elegant ES6 code please? – Mr. Benedict Sep 27 '16 at 22:00
  • When you get that error on the second `reduce` (the inner one), it means `data2` has an entry without `relatedId` (or it exists, but has value `undefined`). If you cannot fix `data2`, then in my solution replace `obj.relatedId.reduce` by `(obj.relatedId || []).reduce`. If the error is on the first `reduce`, then your `data2` variable is undefined, which would mean you didn't use the correct variable name. – trincot Sep 28 '16 at 07:52
  • Thanks for responding @trincot, I'll give that a try. I think the variables are correct. I'll try this instead `(obj.relatedId || []).reduce` – Mr. Benedict Sep 28 '16 at 13:43
1

You can loop existingArray and add item6 with map(), and to create that array you can first use filter() and some() to filter objects that contain same values in relatedId as current object in existingArray and then just use map() to return only names and return object.

var existingArray = [{"item1":"Blah1","item2":"blah2","item3":["0200","0300"],"item4":"blah4","item5":"blah5"},{"item1":"Blah1","item2":"blah2","item3":["0100","0300"],"item4":"blah4","item5":"blah5"},{"item1":"Blah1","item2":"blah2","item3":["0100"],"item4":"blah4","item5":"blah5"},{"item1":"Blah1","item2":"blah2","item3":["0300"],"item4":"blah4","item5":"blah5"},{"item1":"Blah1","item2":"blah2","item3":["0200","0100"],"item4":"blah4","item5":"blah5"}];
var data2 = [{"CandidateName":"Mary","relatedId":["0100","0200"]},{"CandidateName":"John","relatedId":["0200"]},{"CandidateName":"Peter","relatedId":["0300","0100"]},{"CandidateName":"Paul","relatedId":["0300"]}];

var result = existingArray.map(function(o) {
  o.item6 = data2.filter(function(e) {
    return o.item3.some(function(a) {
      return e.relatedId.indexOf(a) != -1;
    })
  }).map(function(e) {
    return e.CandidateName;
  })
  return o;
})

console.log(result)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
  • Thanks for the provision of this answer. I have actually used this answer as opposed to the elegant ES6 answer as I am able to handle if property is undefined. Between your code and @trincot 's code, I have managed to finally solve it. Many thanks. – Mr. Benedict Sep 27 '16 at 22:03