2

I have the following array of objects:

[
   {
     message: 'This is a test',
     from_user_id: 123,
     to_user_id: 567
   },
   {
     message: 'Another test.',
     from_user_id: 123,
     to_user_id: 567
   },
   {
     message: 'A third test.',
     from_user_id: '456',
     to_user_id: 567
   }
]

How do I construct a new array of objects where the outermost object key is based on a common key found in the original array?

This is what I'm after:

[
  {
    123: [
      {
        message: 'This is a test',
        from_user_id: 123,
        to_user_id: 567
      },
      {
        message: 'Another test.',
        from_user_id: 123,
        to_user_id: 567
      }
    ]
  },
  {
    456: [
      {
        message: 'A third test.',
        from_user_id: '456',
        to_user_id: 567
      }
    ]
  }
]

Notice how in the first array, the user ID of 123 shows up in two objects. That would be the object key for the first element in the new array.

Farid
  • 1,557
  • 2
  • 21
  • 35

4 Answers4

5

You could use an object and take the from_user_id property as key for the object. Then push the actual object to the group. For getting the final result, iterate the keys of groups and build a new object for any group.

var data = [{ message: 'This is a test', from_user_id: 123, to_user_id: 567 }, { message: 'Another test.', from_user_id: 123, to_user_id: 567 }, { message: 'A third test.', from_user_id: '456', to_user_id: 567 }],
    groups = Object.create(null),
    result;

data.forEach(function (a) {
    groups[a.from_user_id] = groups[a.from_user_id] || [];
    groups[a.from_user_id].push(a);    
});

result = Object.keys(groups).map(function (k) {
    var temp = {};
    temp[k] = groups[k];
    return temp;
});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Together with a single loop approach

var data = [{ message: 'This is a test', from_user_id: 123, to_user_id: 567 }, { message: 'Another test.', from_user_id: 123, to_user_id: 567 }, { message: 'A third test.', from_user_id: '456', to_user_id: 567 }],
    result = data.reduce(function (groups) {
        return function (r, a) {
            var temp = {};
            if (!groups[a.from_user_id]) {
                groups[a.from_user_id] = [];
                temp[a.from_user_id] = groups[a.from_user_id]; 
                r.push(temp);
            }
            groups[a.from_user_id].push(a);
            return r;
        };
    }(Object.create(null)), []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Can you explain how is this works => *`groups[a.from_user_id] = groups[a.from_user_id] || [];`* – prasanth Jun 09 '17 at 09:26
  • if the key `a.from_user_id` does not exist in the object, it returns `undefined` and then the default value, an empty array is taken and assigned. the `||` is a logical OR, sometimes called *default operator*. – Nina Scholz Jun 09 '17 at 09:29
  • Since you cannot `.push()` on `undefined` but only on an array empty or not, we need to ensure that the `groups[...]` is either something or an empty list. – vassiliskrikonis Jun 09 '17 at 09:40
  • Nice approach, but this will return an object and not an array!!! The OP wants to have an array as results. – cнŝdk Jun 09 '17 at 09:49
  • 1
    @NinaScholz Well done, it's really a piece of art :) – cнŝdk Jun 09 '17 at 10:03
  • @NinaScholz, such an awesome answer. And thanks for showing the first approach (returning an object)! `groups[message.from_user_id] = groups[message.from_user_id] || []` sets up the object’s key, where `message.from_user_id` is the key if it exists, and an empty array if it does not exist. To set up the value, it looks like you’re doing `groups[message.from_user_id].push(message)`, which I’m not really familiar with. I thought `.push()` could only be expected on an array. Isn’t `groups[message.from_user_id]` just an object key (with an empty value)? – Farid Jun 10 '17 at 06:50
  • Since the second method only contains one loop, is it more performant? – Farid Jun 10 '17 at 06:54
  • @Farid, if you take the value of an object with a keys, which does not exist, then you get `undefined` and that is a falsy value for the check with the coming logical OR `||`. in this case an empty array is assigned as a property of the object. the later push works then with this array. depending on implementation, a single loop is better if there are no other loops involved. – Nina Scholz Jun 10 '17 at 08:10
4

You could go full functional get all the key filter it out and map it back as a json object

var b = a.map(key => key['from_user_id'])
var c = {}
b.map(elt => c[elt] = a.filter(k => k.from_user_id == elt))
console.log(c)
Hamuel
  • 633
  • 5
  • 16
  • Thanks! Even though there are two loops, it's done in [what seems like] fewer lines of code, which is always great. – Farid Jun 11 '17 at 05:22
1

var users = [
             {
               message: 'This is a test',
               from_user_id: 123,
               to_user_id: 567
             },
             {
               message: 'Another test.',
               from_user_id: 123,
               to_user_id: 567
             },
             {
               message: 'A third test.',
               from_user_id: '456',
               to_user_id: 567
             }
          ];
console.log(_.groupBy(users,'from_user_id'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Using Lodash will make it super easy for you. Assuming you have :

var users = [
             {
               message: 'This is a test',
               from_user_id: 123,
               to_user_id: 567
             },
             {
               message: 'Another test.',
               from_user_id: 123,
               to_user_id: 567
             },
             {
               message: 'A third test.',
               from_user_id: '456',
               to_user_id: 567
             }
          ];

just in one line with Lodash and you'll get want you want exactly:

users = _.groupBy(users,'from_user_id')
SeleM
  • 9,310
  • 5
  • 32
  • 51
1

You just need to initialize a results array, loop over your data array and check if the iterated from_user_id exists in the results array, push the iterated object on it, otherwise create a new object with the new from_user_id key.

This is how should be your code:

var results = [];
arr.forEach(function(obj){
    let id = obj["from_user_id"];
    if(!results.some(function(r){
       return r[id];
    })){
        let el = {}; 
        el[id] = [];
        el[id].push(obj);
        results.push(el);
    }else{
        results.forEach(function(res){
            if(res[id]){
              res[id].push(obj);
            }
        });
    }
});

Demo:

var arr = [{
  message: 'This is a test',
  from_user_id: 123,
  to_user_id: 567
}, {
  message: 'Another test.',
  from_user_id: 123,
  to_user_id: 567
}, {
  message: 'A third test.',
  from_user_id: 456,
  to_user_id: 567
}];


var results = [];
arr.forEach(function(obj){
    let id = obj["from_user_id"];
    if(!results.some(function(r){
       return r[id];
    })){
        let el = {}; 
        el[id] = [];
        el[id].push(obj);
        results.push(el);
    }else{
        results.forEach(function(res){
            if(res[id]){
              res[id].push(obj);
            }
        });
    }
});
console.log(results);
cнŝdk
  • 31,391
  • 7
  • 56
  • 78
  • Thanks! :) I was sort of trying to avoid a double loop, but it seems to be the most common way. – Farid Jun 11 '17 at 05:55