2

I need to reduce an array of objects, but reduce on a specific key property in the object, and have all objects with the same key value placed into an array in the associated value array.

An example of what I need:

Objects

var obj1 = {
  name: "server1",
  type: "http",
  port: "8080"
}

var obj2 = {
  name: "server2",
  type: "https",
  port: "8443"
}

var obj3 = {
  name: "server3",
  type: "http",
  port: "80"
}

// Place objects in an array (as interm step)
var array = [obj1, obj2, obj3];

With the above code, I tried

var map = new Map(array.map(server => [server.type, server]));

but this ends up giving me:

0: {"http" => Object}
    key: "http"
    value:
        name: "server3"
        port: "80"
        type: "http"
1: {"https" => Object}
    key: "https"
    value:
        name: "server2"
        port: "8443"
        type: "https"

but what I need is:

0: {"http" => Object}
    key: "http"
    value:[ 
        {
            name: "server1"
            port: "8080"
            type: "http"
        },
        {
            name: "server3"
            port: "80"
            type: "http"
        },
1: {"https" => Object}
    key: "https"
    value:
        name: "server2"
        port: "8443"
        type: "https"

So I can go through each type, find all unique values, create lists of each object with this as a type, then add this to a map but it seems too much for a simple task.

Is there a faster/more convenient way to shorten this?

CybeX
  • 2,060
  • 3
  • 48
  • 115

3 Answers3

3

I'm not sure a map would be an appropriate function here. You could use a simple forEach and check if the current key already exists in your array. If it doesn't, you create a new object with the current item and check the next one.

Something like this:

var obj1 = {
  name: "server1",
  type: "http",
  port: "8080"
}

var obj2 = {
  name: "server2",
  type: "https",
  port: "8443"
}

var obj3 = {
  name: "server3",
  type: "http",
  port: "80"
}

// Place objects in an array (as interm step)
var array = [obj1, obj2, obj3];

let output = {};
array.forEach((item) => {
  // the item does not exists, we create it.
  if(!output[item.type]) {
    output[item.type] = {key: item.type, value: []};
  }
  // in either case, we push the current item in the value.
  // of the current output key.
  output[item.type].value.push(item);
});

// we are using Object.values() because we do not want the keys
// used to generate the output.
console.log(Object.values(output));
Nicolas
  • 8,077
  • 4
  • 21
  • 51
  • 1
    for completeness sake, since I intended to use `map.forEach()`, I used `let output = new Map()` instead of `let output = {};` which allows me to use `forEach()` comfortably – CybeX Jan 23 '20 at 20:25
1

This is the perfect use-case for reduce(). The format is:

arr.reduce(callback( accumulator, currentValue[, index[, array]] )[, initialValue])

We would set our initialValue argument to an empty object ({}). Our callback function will then take each item in the array and add it to the correct array in our categorised object. See below:

var obj1 = {
  name: "server1",
  type: "http",
  port: "8080"
}

var obj2 = {
  name: "server2",
  type: "https",
  port: "8443"
}

var obj3 = {
  name: "server3",
  type: "http",
  port: "80"
}

// Place objects in an array (as interm step)
var array = [obj1, obj2, obj3];

const result = array.reduce((categorisedObjs, currentItem) => {
  // If the key does not yet exist in our object then create it
  // and initialize it with an empty array
  if (!categorisedObjs[currentItem.type]) categorisedObjs[currentItem.type] = []

  // Add the object to the correct array
  categorisedObjs[currentItem.type].push(currentItem);

  // Return the categorised objects for the next iteration
  return categorisedObjs;
}, {});

console.log(JSON.stringify(result, true, 2));

This will produce the exact result desired and it's relatively simple to understand:

{
  "http": [
    {
      "name": "server1",
      "type": "http",
      "port": "8080"
    },
    {
      "name": "server3",
      "type": "http",
      "port": "80"
    }
  ],
  "https": [
    {
      "name": "server2",
      "type": "https",
      "port": "8443"
    }
  ]
}
Ivan Kahl
  • 598
  • 2
  • 11
1

I think this is how you can achieve it

var array = [obj1, obj2, obj3];
var newarray = [];
$.each(array, function( index, value ) {

 if(!newarray[value.type]) {
   newarray[value.type] = {data:[]};
 }
 newarray[value.type].data.push(value);
});

console.log(newarray);
Amit Sharma
  • 1,775
  • 3
  • 11
  • 20
  • I don't quite understand your code, There is an unused variable `newarray`, your are trying to access a string key in a array, whose keys are numbers. And you are suggesting JQuery when there was not mention of that library in OP's question. I don't want to be rude, i just feel like your answer could use some improvement. – Nicolas Jan 23 '20 at 13:26
  • 1
    I have updated the code. Sorry pasted the wrong one – Amit Sharma Jan 23 '20 at 13:29