Kevin Bowersox
Kevin Bowersox

Reputation: 94499

Concatenate Nested Array after Mapping

I have an array with several category objects, each of which has an items property containing an array of item objects. I want to map each item in each category to an object[] with objects that have the properties value and label. For some reason, I can't perform the concatenation.

var categories = [{
                name: "category1",
                items: [{
                    itemId: 1,
                    name: "Item1"
                }, {
                    itemId: 2,
                    name: "Item2"
                }]
            }, {
                name: "category2",
                items: [{
                    itemId: 3,
                    name: "Item3"
                }, {
                    itemId: 4,
                    name: "Item4"
                }]
            }];

var items = [];
for(var i = 0; i < categories.length; i++){
    items.concat($.map(categories[i].items,function(elem){
        return {value:elem.itemId, label:elem.name};
    }));
} 
console.log(items); //prints []

Expected Result

[{
   label: "Item1",
   value: "1"
},
{
   label: "Item2",
   value: "2"
},{
   label: "Item3",
   value: "3"
},{
   label: "Item4",
   value: "4"
}

I feel as if I am missing something very basic. I logged the result of the $.map function and it appears to be returning an []. Can anyone figure out the issue?

JSFiddle: http://jsfiddle.net/vymJv/

Upvotes: 16

Views: 45936

Answers (6)

ncesar
ncesar

Reputation: 1802

Updated with flatMap (not compatible with IE):

categories.flatMap((categories) => categories.items)

flatMap() method returns a new array formed by applying a given callback function to each element of the array, and then flattening the result by one level.

Upvotes: 17

  const items = categories
          .map(category => category.items)
          .reduce((prev, current) => [...prev, ...current])
          .map((e, i) => ({ label: e.name, value: e.itemId }));

Upvotes: 4

A. Wolff
A. Wolff

Reputation: 74420

The concat() method is used to join two or more arrays.

This method does not change the existing arrays, but returns a new array, containing the values of the joined arrays.

http://jsfiddle.net/vymJv/1/

for(var i = 0; i < categories.length; i++){
    items = items.concat($.map(categories[i].items, function(elem) {
        return {value: elem.itemId, label: elem.name};
    }));
}

Upvotes: 10

luxcoder
luxcoder

Reputation: 41

This piece of code solves your task using a functional programming approach:

var items = [].concat.apply([], categories.map(cat =>
  cat.items.map(elem => ({ value:elem.itemId, label:elem.name })))
)

Explanation: Function.prototype.apply() has the syntax fun.apply(thisArg, [argsArray]) and lets us provide parameters to a function in an array. Array.prototype.concat() combines an arbitrary amount of parameters into one array. If we now write Array.prototype.concat.apply([], [category1Items, ..., categoryNItems]), it actually equals [].concat(category1Items, ..., categoryNItems), which concatenates all parameters together. You can also replace Array.prototype.concat by [].concat to keep it shorter. Otherwise we just use standard mapping to get the job done.

You could also split the code apart a bit more for clarity:

function getItem(elem){
  return {value:elem.itemId, label:elem.name};
}

function getCategoryItems(cat) {
  return cat.items.map(getItem);
}

function flatten(arr) {
  return Array.prototype.concat.apply([], arr);
}

var items = flatten(categories.map(getCategoryItems));

Upvotes: 4

itsbriany
itsbriany

Reputation: 192

We could extend the array prototype by creating concatAll which will concatenate all of your arrays by iterating over each subarray, dumping each value into a new array.

Array.prototype.concatAll = function() {
  var results = [];
  this.forEach(function(subArray) {
    subArray.forEach(function(subArrayValue) {
      results.push(subArrayValue);
    });
  });
  return results;
};

Then, we can get the desired result with the following:

let items = categories.map(function(category) {
  return category.items.map(function(item) {
    return {label: item.name, value: item.itemId};
  });
}).concatAll();

We get the items by translating each category into an array of items. Because we have two categories, we will end up with two arrays of items. By applying concatAll on the final result, we flatten those two arrays of items and get the desired output.

Upvotes: 3

y2knoproblem
y2knoproblem

Reputation: 570

Another method using straight Javascript:

var x = categories.map(function(val) {
  return val.items;
}).reduce(function(pre, cur) {
   return pre.concat(cur);
}).map(function(e,i) {
  return {label:e.name,value:e.itemId};
});

Output: x = [{label: "Item1", value: 1}, {label: "Item2", value: 2}, …]

Upvotes: 29

Related Questions