-4

With Javascript, I want to split an array and group by an attribute

input:

[
    {
        "type": "typeA",
        "label": "labelA",
        "placeholders": [
            "b",
            "a",
            "r"
        ]
    },{
        "type": "typeB",
        "label": "labelB",
        "placeholders": [
            "x",
            "y",
            "z"
        ]
    },{
        "type": "typeA",
        "label": "labelAAA",
        "placeholders": [
            "a",
            "b",
            "c"
        ]
    }
]

I want output:

[
  {
    "type": "typeA",
    "items": [
      {
        "label": "labelA",
        "placeholders": [
          "b",
          "a",
          "r"
        ]
      },
      {
        "label": "labelAAA",
        "placeholders": [
          "a",
          "b",
          "c"
        ]
      }
    ]
  },
  {
    "type": "typeB",
    "items": [
      {
        "label": "labelB",
        "placeholders": [
          "b",
          "a",
          "r"
        ]
      }
    ]
  }
]

I think javascript use .reduce, .concat, .map, ...

I'd like to do this without additional libraries (e.g., lodash).

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
Stéphane GRILLON
  • 11,140
  • 10
  • 85
  • 154
  • 4
    So, with 10k+ rep, at least show us your attempt... – 0stone0 Jun 13 '23 at 15:54
  • 1
    Does this answer your question? [How can I group an array of objects by key?](https://stackoverflow.com/questions/40774697/how-can-i-group-an-array-of-objects-by-key) – 0stone0 Jun 13 '23 at 15:54
  • @0stone0, I try modify this post: https://stackoverflow.com/questions/40774697/how-can-i-group-an-array-of-objects-by-key – Stéphane GRILLON Jun 13 '23 at 15:56
  • 1
    Well show us your attempt, you should know that this is not some free coding service.... [How much research effort is expected of Stack Overflow users?](https://meta.stackoverflow.com/questions/261592/how-much-research-effort-is-expected-of-stack-overflow-users) – 0stone0 Jun 13 '23 at 15:57
  • What specific issue are you having? Seems like it's a matter of iteration (manually or via reduce) and adding each object to its type-based bucket. – Dave Newton Jun 13 '23 at 16:00
  • since you're organizing by `type` - which means types are unique, an array response is useless, you would much better go with an Object response where the type name is the key. – Roko C. Buljan Jun 13 '23 at 16:01
  • @RokoC.Buljan, I am looking for how to do what is asked in the question and not how to do otherwise. – Stéphane GRILLON Jun 13 '23 at 16:03
  • @0stone0, I am looking for a clean solution in javascript that there are surely people who do javascript 100% of their time. this is not my case. If you don't have the solution, you can simply move on. – Stéphane GRILLON Jun 13 '23 at 16:05
  • 2
    @StéphaneGRILLON I think the point was that there's a certain amount of effort required rather than saying "here's the problem, write the code". Tangential: this is almost certainly a dupe, I might take a few beats and search for something like "group objects by property" or something similar. While I might have phrased it differently the original comments are on-point, and it seems like you've been here long enough to grok this. – Dave Newton Jun 13 '23 at 16:07

4 Answers4

1

One of the best ways to approach this is to reduce the array into a Map and group by type.

Calling Map.prototype.values() returns an iterator, so you will need to convert to an array via:

  • Array.from(iterator) or
  • [...iterator] (spread oprator)

const input = [{
  "type": "typeA",
  "label": "labelA",
  "placeholders": ["b", "a", "r"]
}, {
  "type": "typeB",
  "label": "labelB",
  "placeholders": ["x", "y", "z"]
}, {
  "type": "typeA",
  "label": "labelAAA",
  "placeholders": ["a", "b", "c"]
}];

const output = Array.from(
  input.reduce((typeMap, { type, ...rest }) => {
    if (!typeMap.has(type)) {
      typeMap.set(type, { type, items: [ { ...rest } ] });
    } else {
      typeMap.get(type).items.push({ ...rest });
    }
    return typeMap;
  }, new Map).values());

console.log(output);
.as-console-wrapper { top: 0; max-height: 100% !important; }
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
0

Here is an example I created in JSFiddle that should get you the result you're looking for.

https://jsfiddle.net/9kman64x/

const input = [  
  {  
    "type": "typeA",  
    "label": "labelA",  
    "placeholders": ["b", "a", "r"]  
  },  
  {  
    "type": "typeB",  
    "label": "labelB",  
    "placeholders": ["x", "y", "z"]  
  },  
  {  
    "type": "typeA",  
    "label": "labelAAA",  
    "placeholders": ["a", "b", "c"]  
  }  
];  
 //You can See example here
 //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
const output = input.reduce((acc, item) => {  
  // Find the existing group for the current item's type  
  const group = acc.find(g => g.type === item.type);  
  
  // If the group exists, add the item to it  
  if (group) {  
    group.items.push({  
      label: item.label,  
      placeholders: item.placeholders  
    });  
  } else {  
    // If the group doesn't exist, create a new one and add it  
    acc.push({  
      type: item.type,  
      items: [  
        {  
          label: item.label,  
          placeholders: item.placeholders  
        }  
      ]  
    });  
  }  
  return acc;  
}, []);  
  
console.log(output); 

It uses the reduce method on the array and either adds it to a known group or makes a new one.

0

As I said in comments, since you're grouping by type - making it basically unique per se, you would be better going for an Object as response (instead of array). It's easier to do so, and easier to retrieve data from:

const arr = [
  {"type": "typeA","label": "labelA","placeholders": ["b","a","r"]},
  {"type": "typeB","label": "labelB","placeholders": ["x","y","z"]},
  {"type": "typeA","label": "labelAAA","placeholders": ["a","b","c"]}
];

const groups = arr.reduce((acc, ob) => {
  acc[ob.type] ??= {type: ob.type, items: []};
  acc[ob.type].items.push(ob);
  return acc
}, {});

console.log(groups)

then, to get the length of your groups use:

const groupsTot = Object.keys(groups).length;

or to get an Array (as requested, but which I think is not necessary), use:

const groupsArray = [Object.values(groups)];
Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
0

You could do something like this:

const groupedArray = originalArray.reduce(([acc, curr]) => {
  const { type: currentType, ...rest } = curr;

  // Check if the type exists in the grouping
  const indexOfCurrentType = acc.findIndex(({ type }) => type === currentType);
  
  // This grouping already exists. Let's add to the items array
  if (indexOfCurrentType > -1) {
    acc[indexOfCurrentType].items.push(rest);
  } else {
    acc.push({
      type: currentType,
      items: [rest],
    })
  }

  return acc;
}, []);

The idea is to incrementally build the new array, checking if we have already seen a certain type. If we have, add it to the list of items; otherwise, create a new entry. It's not the most optimal solution because we have to loop through every type to see if we have a match on each iteration.

sumowrestler
  • 345
  • 5
  • 19