2

Suppose I have this array:

[
    {"type": "A",   "status": "CREATED",        "name": "Jack"}, 
    {"type": "A",   "status": "CREATED",        "name": "John"}, 
    {"type": "A",   "status": "UPDATED",        "name": "Alex"}, 
    {"type": "B",   "status": "UPDATED",        "name": "Jane"}
]

and I want to have a new array grouped by "type" and "status" and have the names in a list. This is the desired output:

[
    {"type": "A",   "status": "CREATED",        "name-list": ["Jack", "John"]}, 
    {"type": "A",   "status": "UPDATED",        "name-list": ["Alex"]}, 
    {"type": "B",   "status": "UPDATED",        "name-list": ["Jane"]}
]

as you can see the first object of the array contains 2 names because belong to the same "type" and "status". (name-list is just an example, could also remain name, the important thing is that it should be an array of strings/objects)

I have to represent these data in an html page and so needs to be able to cycle them via JavaScript.

1
  • If performance is something that matters for your app, you may check out the benchmark Commented Jul 22, 2020 at 16:13

5 Answers 5

3

You may traverse your source array with Array.prototype.reduce() building up the Map, having type and status combined as a key and respective object, with merged name as a value. Then extract Map.prototype.values() from that Map:

const src = [{"type":"A","status":"CREATED","name":"Jack"},{"type":"A","status":"CREATED","name":"John"},{"type":"A","status":"UPDATED","name":"Alex"},{"type":"B","status":"UPDATED","name":"Jane"}],

     result = [...src
        .reduce((r, o) => {
          const key = o.type+'\ud8ff'+o.status,
                match = r.get(key)
          match ? match.name.push(o.name) : r.set(key, {...o, name: [o.name]})
          return r
        }, new Map())
        .values()
      ]
      
console.log(result)
.as-console-wrapper{min-height:100%;}

Sign up to request clarification or add additional context in comments.

2 Comments

Tha's pretty cool, how about if I have an additional integer parameter in each object, that I want to sum when grouping the names?
@andQlimax : that would be something, as simple as that
2

I would try with .reduce() and .find():

const data = [
    {"type": "A",   "status": "CREATED",        "name": "Jack"}, 
    {"type": "A",   "status": "CREATED",        "name": "John"}, 
    {"type": "A",   "status": "UPDATED",        "name": "Alex"}, 
    {"type": "B",   "status": "UPDATED",        "name": "Jane"}
];

const result = data.reduce((a, { type, status, name}) => {
  const found = a.find(e => e.type === type && e.status === status);
  
  if (found) {
    found['name-list'].push(name);
  } else {
    a.push({
      type,
      status,
      ['name-list']: [name]
    });
  }
  
  return a;
}, []);

console.log(result);

2 Comments

Doing nested lookups (.find()'s inside .reduce() may slow down the performance should source array get large enough.
@YevgenGorbunkov Agree, checked your answer, that's pretty smart, also upvoted that.
1

const arr = [
    {"type": "A",   "status": "CREATED",        "name": "Jack"}, 
    {"type": "A",   "status": "CREATED",        "name": "John"}, 
    {"type": "A",   "status": "UPDATED",        "name": "Alex"}, 
    {"type": "B",   "status": "UPDATED",        "name": "Jane"}
]

const res = arr.reduce((acc, cur) => {
  
  acc.forEach((obj, idx) => {
      if(obj.type == cur.type && obj.status == cur.status){
          acc[idx]["name-list"].push(cur.name)
      }
  })
  
  if(!acc.some(obj => obj["name-list"].includes(cur.name))){
      acc.push({type: cur.type, status: cur.status, "name-list": [cur.name]})
  }
  
  return acc
},[])

console.log(res)

Comments

0

Here is what you want:

const ar = [
    { "type": "A", "status": "CREATED", "name": "Jack" },
    { "type": "A", "status": "CREATED", "name": "John" },
    { "type": "A", "status": "UPDATED", "name": "Alex" },
    { "type": "B", "status": "UPDATED", "name": "Jane" }
];
const res = Array.from(ar.reduce((a, c) => {
    if (a.has(c.type + c.status)) {
        a.get(c.type + c.status)['name-list'].push(c.name);
    } else {
        c['name-list'] = [c.name];
        delete c.name;
        a.set(c.type + c.status, c)
    }
    return a;
}, new Map()).values());

console.log(res);

1 Comment

Using type and status concatenated as a Map key is somewhat dangerous, as you may get merged unexpectedly objects with {type: 'a', status: 'bc'..} and {type: 'ab', status: 'c'..}. You'd be much better off using some safe character as delimiter. You may refer my ansewr for example.
0

Using reduce and Object.values

let arr = [
    {"type": "A",   "status": "CREATED",        "name": "Jack"}, 
    {"type": "A",   "status": "CREATED",        "name": "John"}, 
    {"type": "A",   "status": "UPDATED",        "name": "Alex"}, 
    {"type": "B",   "status": "UPDATED",        "name": "Jane"}
];

arr = Object.values(
    arr.reduce((acc, cur) => {
        if(acc[`${cur.type}-${cur.status}`]) acc[`${cur.type}-${cur.status}`]["name-list"].push(cur.name)

        else {
            acc[`${cur.type}-${cur.status}`] = {type:cur.type, status:cur.status, "name-list":[cur.name]}
        }    
        return acc;
    }, {})
);

console.log(arr)

1 Comment

Using hyphen as delimiter is sort of risky as you, would get merged unexpectedly objects with {type: 'a-b', status:'c..} and `{type: 'a', status: 'b-c'..}. You should use something more safe as delimiter, you may refer to my answer for example.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.