Perform .join on value in array of objects

If I have an array of strings, I can use the .join() method to get a single string, with each element separated by commas, like so:

["Joe", "Kevin", "Peter"].join(", ") // => "Joe, Kevin, Peter"

I have an array of objects, and I’d like to perform a similar operation on a value held within it; so from

[
  {name: "Joe", age: 22},
  {name: "Kevin", age: 24},
  {name: "Peter", age: 21}
]

perform the join method only on the name attribute, to achieve the same output as before.

Currently I have the following function:

function joinObj(a, attr){
  var out = [];

  for (var i = 0; i < a.length; i++){
    out.push(a[i][attr]);
  }

  return out.join(", ");
}

There’s nothing wrong with that code, it works, but all of a sudden I’ve gone from a simple, succinct line of code to a very imperative function. Is there a more succinct, ideally more functional way of writing this?


Solution 1:

If you want to map objects to something (in this case a property). I think Array.prototype.map is what you're looking for if you want to code functionally.

console.log([
      {name: "Joe", age: 22},
      {name: "Kevin", age: 24},
      {name: "Peter", age: 21}
    ].map(function(elem){
        return elem.name;
    }).join(","));

In modern JavaScript:

console.log([
      {name: "Joe", age: 22},
      {name: "Kevin", age: 24},
      {name: "Peter", age: 21}
    ].map(e => e.name).join(","));

(fiddle)

If you want to support older browsers, that are not ES5 compliant you can shim it (there is a polyfill on the MDN page above). Another alternative would be to use underscorejs's pluck method:

var users = [
      {name: "Joe", age: 22},
      {name: "Kevin", age: 24},
      {name: "Peter", age: 21}
    ];
var result = _.pluck(users,'name').join(",")

Solution 2:

Well you can always override the toString method of your objects:

    var arr = [
        {name: "Joe", age: 22, toString: function(){return this.name;}},
        {name: "Kevin", age: 24, toString: function(){return this.name;}},
        {name: "Peter", age: 21, toString: function(){return this.name;}}
    ];
         
    var result = arr.join(", ");
    console.log(result);

Solution 3:

I've also come across using the reduce method, this is what it looks like:

console.log(
    [
        {name: "Joe", age: 22},
        {name: "Kevin", age: 24},
        {name: "Peter", age: 21}
    ]
    .reduce(function (a, b) {
        return (a.name || a) + ", " + b.name}
    )
)

The (a.name || a) is so the first element is treated correctly, but the rest (where a is a string, and so a.name is undefined) isn't treated as an object.

Edit: I've now refactored it further to this:

x.reduce(function(a, b) {return a + ["", ", "][+!!a.length] + b.name;}, "");

which I believe is cleaner as a is always a string, b is always an object (due to the use of the optional initialValue parameter in reduce)

Edit 6 months later: Oh what was I thinking. "cleaner". I've angered the code Gods.

Solution 4:

On node or ES6+:

users.map(u => u.name).join(', ')

Solution 5:

I don't know if there's an easier way to do it without using an external library, but I personally love underscore.js which has tons of utilities for dealing with arrays, collections etc.

With underscore you could do this easily with one line of code:

_.pluck(arr, 'name').join(', ')