0

I have two arrays that I want to join conditionally:

aData = [ { id: 1, title: `bla`}, { id: 2, title: `la`}, { id: 3, title: `lala`} ]
bData = [ { id: 1, description: `bla`} ]

In the first case I want to be able to get only matching result (aData+bData) with merged properties

matchingIdsResult = [ { id: 1, title: `bla`, description: `bla` } ]

IN the second case I want the unmatching result - just objects from aData with ids that didn't ocour in bData:

unmathingIdsResult = [ { id: 2, title: `la`}, { id: 3, title: `lala`} ]

I was playing with .map and .reduce and achieve this so far:

const data: any = aData.concat(bData).reduce((acc, x) => {
    acc[x.id] = Object.assign(acc[x.id] || {}, x);
    return acc;
}, {});

However in that case I do get all aData objects merged with matching bData properties so far which is not really what I want.

Sergino
  • 10,128
  • 30
  • 98
  • 159

3 Answers3

1

Create a Map by id from bData. Iterate aData with reduce. If an object id is in the bDataMap push the combined object into the 1st sub array. If not, to the 2nd. Get the matchingIdsResult and unmatchingIdsResult by destructuring the results of the reduce:

const aData = [{"id":1,"title":"bla"},{"id":2,"title":"la"},{"id":3,"title":"lala"}];
const bData = [{"id":1,"description":"bla"}];

const bDataMap = new Map(bData.map((o) => [o.id, o]));

const [matchingIdsResult, unmatchingIdsResult] = aData.reduce((r, o) => {
  if(bDataMap.has(o.id)) r[0].push(Object.assign({}, o, bDataMap.get(o.id)));
  else r[1].push(o);
  
  return r;
}, [[], []]);
  
console.log(matchingIdsResult);
console.log(unmatchingIdsResult);

If you want to skip the creation of bDataMap, you can use Array.find(). The complexity would become O(n * m) instead of o(n + m) with the Map (n - aData.length, m - bData.length), but that might be negligible with small arrays:

const aData = [{"id":1,"title":"bla"},{"id":2,"title":"la"},{"id":3,"title":"lala"}];
const bData = [{"id":1,"description":"bla"}];

const [matchingIdsResult, unmatchingIdsResult] = aData.reduce((r, o) => {
  const match = bData.find(({ id }) => id === o.id);

  if(match) r[0].push(Object.assign({}, o, match));
  else r[1].push(o);
  
  return r;
}, [[], []]);
  
console.log(matchingIdsResult);
console.log(unmatchingIdsResult);
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
1

You could take the first array as unmatching start values and check the second and either split matching objects or push to unmatching array.

var aData = [{ id: 1, title: 'bla' }, { id: 2, title: 'la' }, { id: 3, title: 'lala' }],
    bData = [{ id: 1, description: 'bla' }],
    unmatching = aData,
    matching = [];

bData.forEach(o => {
    var index = unmatching.findIndex(({ id }) => o.id = id);
    if (index === -1) {
        unmatching.push(o);
    } else {
        matching.push(Object.assign({}, unmatching.splice(index, 1)[0], o));
    }
});

console.log(matching);
console.log(unmatching);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

Create a map of unmatched and matched checking simply if id exists or not.

And when you've got two separate awways of matched and unmatched, simply merge the matched array (on the basis of ID) with the other array containing the data.

aData = [ { id: 1, title: `bla`}, { id: 2, title: `la`}, { id: 3, title: `lala`} ]
bData = [ { id: 1, description: `bla`} ];
var unmatched = [],
   matched = [];
aData.every(x=> bData.map(e=> e.id).includes(x.id) ? matched.push(x) : unmatched.push(x))
matched = matched.map(x=> Object.assign(x, bData.find(e=> e.id ===x.id )))
console.log("Matched " , matched)

console.log("Un matched", unmatched)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Muhammad Usman
  • 10,039
  • 22
  • 39