node.jsarraysjavascript-objects

In Node how to merge an array of objects into a new array where each object name has it's values in an array of strings?


I have an array of objects that looks like:

const arr = [
    {
        "name": "Day",
        "value": "Monday"
    },
    {
        "name": "Month",
        "value": "November"
    },
    {
        "name": "Day",
        "value": "Monday"
    },
    {
        "name": "Month",
        "value": "December"
    },
    {
        "name": "Day",
        "value": "Friday"
    },
    {
        "name": "Month",
        "value": "November"
    },
    {
        "name": "Day",
        "value": "Friday"
    },
    {
        "name": "Month",
        "value": "December"
    }
]

but I'm needing to modify my array of objects to:

const final = [
    {
        "name": "Day",
        "values": [
            "Monday",
            "Friday"
        ]
    },
    {
        "name": "Month",
        "values": [
            "November",
            "December"
        ]
    }
]

I can filter out the duplicates with:

const filtered = arr.filter(
    (v, i, a) =>
      a.findIndex(v2 => v?.name === v2?.name && v?.value === v2?.value) === i
  );

but I'm stuck on the approach on an efficient way to do this without Lodash or underscore and I've tried using Object.assign() but my implementation isn't working:

const test = filtered.map(({ name, value }) =>
    Object.assign({ name, value: [] }, (name, value))
  );
console.log(test)

Thought I had seen this asked before and tested answers from:

In an array of objects and can I combine the objects with the same name but have the value in an array of strings?


Solution

  • Use reduce with a start value that has your days and moths elements preallocated, using a Set to automatically ignore duplicates, and then after you've reduced everything, converting that set into an array:

    const arr = [{
        name: "Day",
        value: "Monday",
      },{
        name: "Month",
        value: "November",
      },{
        name: "Day",
        value: "Monday",
      },{
        name: "Month",
        value: "December",
      },{
        name: "Day",
        value: "Friday",
      },{
        name: "Month",
        value: "November",
      },{
        name: "Day",
        value: "Friday",
      },{
        name: "Month",
        value: "December",
    }];
    
    // reduce the data to the form you need:
    const final = arr.reduce(
      (resultSoFar, {name, value}) => {
        // find the "bin" to add this value to:
        let bin = resultSoFar.find((e) => e.name === name);
        // or make a new bin if there isn't one:
        if (!bin) resultSoFar.push(bin = { name, values: new Set() });
        // then add our value, and return the new result so far.
        bin.values.add(value);
        return resultSoFar;
      },
      // and start with `resultSoFar` being an empty array:
      []
    );
    
    // as last step, convert the sets into plain arrays:
    final.forEach(e => e.values = [...e.values]);
    
    // what did we get?
    console.log(final);