Search code examples
javascriptarraysmergeecmascript-6array-merge

JS (ES6): Merge arrays based on id and concatenating sub arrays


I have two arrays, which look like this:

const persons = [
  {
    id: 1,
    name: 'Peter',
    job: 'Programmer'
  },
  {
    id: 2,
    name: 'Jeff',
    job: 'Architect'
  },
];

const salaries = [
  {
    id: 1,
    salary: 3000,
    departments: ['A', 'B'] 
  },
  {
    id: 1,
    salary: 4000,
    departments: ['A', 'C']
  },
  {
    id: 2,
    salary: 4000,
    departments: ['C', 'D']
  }
];

Now I need to somehow merge this arrays to one, so that every id only exists once. Same keys should be replaced, except it is an array, then I want them to add/concat. So the desired result should look something like this:

const result = [
  {
    id: 1,
    name: 'Peter',
    job: 'Programmer',
    salary: 4000,
    departments: ['A', 'B', 'C'] 
  },
  {
    id: 2,
    name: 'Jeff',
    job: 'Architect',
    salary: 4000,
    departments: ['C', 'D']
  }
];

I have already tried:

// double id's, arrays get replaced
Object.assign({}, persons, salaries)

// loadsh: double id's, arrays get concatenated
_.mergeWith(persons, salaries, (objValue, srcValue) => {
    if (_.isArray(objValue)) {
        return objValue.concat(srcValue);
    }
});

// gives me a map but replaces arrays
new Map(salaries.map(x => [x.id, x])

Does anyone have an idea how to accomplish this?


Solution

  • You can concat the arrays, than combine all items with the same id using Array.reduce(), and a Map.

    to combine objects with the same id, get the object from the Map. Iterate the new Object.entries() with Array.forEach(). Check if existing value is an array, if not assign the value. If it is an array, combine the arrays, and make the items unique using a Set with array spread.

    To convert the Map back to an array, you can spread the Map.values() iterator.

    const persons = [{"id":1,"name":"Peter","job":"Programmer"},{"id":2,"name":"Jeff","job":"Architect"}];
    const salaries = [{"id":1,"salary":3000,"departments":["A","B"]},{"id":1,"salary":4000,"departments":["A","C"]},{"id":2,"salary":4000,"departments":["C","D"]}];
    
    const result = [...persons.concat(salaries)
      .reduce((r, o) => {
        r.has(o.id) || r.set(o.id, {});
        
        const item = r.get(o.id);
        
        Object.entries(o).forEach(([k, v]) =>
          item[k] = Array.isArray(item[k]) ? 
            [...new Set([...item[k], ...v])] : v
        );
        
        return r;
      }, new Map()).values()];
      
    console.log(result);