mcdev
mcdev

Reputation: 171

Javascript - group array of objects by common values with label

I am trying to turn an array of objects into another array of objects by grouping by a specific value and adding that value as label and taking it out of the object in the new array.

Input: So for instance I have this array of objects:

let tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];

Expected: I am looking to try and figure out how I can get to this, where there is a label for each of the unique groupings and options array containing the values of that grouping.

[
  {
    label: "Front End",
    options: [
      { id: 1, value: "HTML" },
      { id: 2, value: "React" },
    ],
  },
  {
    label: "Back End",
    options: [
      { id: 3, value: "Node" },
      { id: 4, value: "PHP" },
    ],
  },
]

The closest I have been able to get to is using reduce to group by the grouping key:

const groupedTech = tech.reduce((acc, value) => {
  // Group initialization
  if (!acc[value.grouping]) {
    acc[value.grouping] = [];
  }
 
  // Grouping
  acc[value.grouping].push(value);
 
  return acc;
}, {});

Which gives me this:

{
  "Front End": [
    { id: 1, grouping: "Front End", value: "HTML" },
    { id: 2, grouping: "Front End", value: "React" },
  ],
  "Back End": [
    { id: 3, grouping: "Back End", value: "Node" },
    { id: 4, grouping: "Back End", value: "PHP" },
  ],
}

But this returns object not an array and doesn't remove the grouping value. I have not been able to figure out how to group properly because in the array of objects I have not found an efficient way to compare against to see if the grouping exists and if so add to that nested array. Would I be better off using something like .map()? Appreciate any leads/learnings!

Upvotes: 13

Views: 12000

Answers (7)

Unmitigated
Unmitigated

Reputation: 89284

We can make use of Object.groupBy as the first step to aggregate the objects by the grouping key for a much simplified solution.

const tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];
const res = Object.entries(Object.groupBy(tech, o => o.grouping))
  .map(([label, v]) => ({label, options: 
    v.map(({grouping, ...rest}) => rest)}));
console.log(res);

Upvotes: 1

Hitesh Dalvi
Hitesh Dalvi

Reputation: 1

const inventory = [
  { name: "asparagus", type: "vegetables", quantity: 5 },
  { name: "bananas", type: "fruit", quantity: 10 },
  { name: "goat", type: "meat", quantity: 40 },
  { name: "cherries", type: "fruit", quantity: 5 },
  { name: "fish", type: "meat", quantity: 22 },
  { name: "fish", type: "cereals", quantity: 100 },
  { name: "fish", type: "meat", quantity: 20 },
  { name: "fish", type: "cereals", quantity: 12 },
];
function func(inventory){
  let type = [], result = {};
  inventory.map((item)=>{type.push(item.type)})
  type = [...new Set(type)]
  for(i=0;i<inventory.length;i++){
   if(type.find(item => item === inventory[i].type)){
       result= {
        ...result,
        [inventory[i].type]: 
        result[type.find(item => item === inventory[i].type)] ? 
        result[type.find(item => item === inventory[i].type)] +inventory[i].quantity
        : inventory[i].quantity


      }
    }
  }
  return result
 }

console.log(func(inventory))

Upvotes: 0

charlietfl
charlietfl

Reputation: 171669

Using a Map and Map#values()

const grouped = tech.reduce((m,{grouping:label, ...rest})=>{
    const group = m.get(label) || {label, options:[]};
    group.options.push({...rest})
    return m.set(label, group)
},new Map)

console.log([...grouped.values()])
<script>
let tech=[{id:1,grouping:"Front End",value:"HTML"},{id:2,grouping:"Front End",value:"React"},{id:3,grouping:"Back End",value:"Node"},{id:4,grouping:"Back End",value:"PHP"}];
</script>

Upvotes: 2

Hao Wu
Hao Wu

Reputation: 20699

You're very close, just wrap the key-value entries of the result you've got in a map function:

let tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];

const groupedTech = Object.entries(
  // What you have done
  tech.reduce((acc, { id, grouping, value }) => {
    // Group initialization
    if (!acc[grouping]) {
      acc[grouping] = [];
    }
    
    // Grouping
    // FIX: only pushing the object that contains id and value
    acc[grouping].push({ id, value });

    return acc;
  }, {})
).map(([label, options]) => ({ label, options }));

console.log(groupedTech);

Upvotes: 11

Phil
Phil

Reputation: 164798

I usually like to build up a Map of key / value pairs then transform those entries into the final result (usually using Array.prototype.map() or Array.from()).

const tech = [
  { id: 1, grouping: "Front End", value: "HTML" },
  { id: 2, grouping: "Front End", value: "React" },
  { id: 3, grouping: "Back End", value: "Node" },
  { id: 4, grouping: "Back End", value: "PHP" },
];

const groupedMap = tech.reduce((map, { grouping, ...option }) => {
  if (!map.has(grouping)) {
    map.set(grouping, [])
  }
  map.get(grouping).push(option)
  return map
}, new Map())

const groupedTech = Array.from(groupedMap, ([ label, options ]) => ({
  label,
  options
}))

console.log(groupedTech)

Upvotes: 2

Nick
Nick

Reputation: 147166

A minor variation on the other two answers if you want to get exactly the output you specify:

let tech = [{
    id: 1,
    grouping: "Front End",
    value: "HTML"
  },
  {
    id: 2,
    grouping: "Front End",
    value: "React"
  },
  {
    id: 3,
    grouping: "Back End",
    value: "Node"
  },
  {
    id: 4,
    grouping: "Back End",
    value: "PHP"
  },
];

const groupedTech = Object.entries(
    tech.reduce((acc, value) => {
      // Group initialization
      if (!acc[value.grouping]) {
        acc[value.grouping] = [];
      }

      // Grouping
      acc[value.grouping].push({
        id: acc[value.grouping].length+1,
        value: value.value
      });

      return acc;
    }, {}))
  .map(([label, options]) => ({
    label,
    options
  }));

console.log(groupedTech);

Upvotes: 2

hgb123
hgb123

Reputation: 14891

You just have to do one more manipulation with Object.entries and .map

let tech = [
  { id: 1, grouping: 'Front End', value: 'HTML' },
  { id: 2, grouping: 'Front End', value: 'React' },
  { id: 3, grouping: 'Back End', value: 'Node' },
  { id: 4, grouping: 'Back End', value: 'PHP' }
]

const groupedTech = tech.reduce((acc, value) => {
  // Group initialization
  if (!acc[value.grouping]) {
    acc[value.grouping] = []
  }

  // Grouping
  acc[value.grouping].push(value)

  return acc
}, {})

const res = Object.entries(groupedTech).map(([label, options]) => ({
  label,
  options
}))

console.log(res)

Upvotes: 2

Related Questions