Create an array which contains specific values of a specific nested object inside an array

So I have this array of objects, which respectively contain other objects:

const template_columns = [
{
"id": 38,
"name": "Ideas",
"position": 0,
"template_id": 4,
"column_type": "Column",
"workflow": [
{
"action": "Next",
"move_to": "Backlog",
"assign_to": "none"
},
{
"action": "Prev",
"move_to": "First column",
"assign_to": "none"
}
],
"_t": "template_column"
},
{
"id": 39,
"name": "Backlog",
"position": 1,
"template_id": 4,
"column_type": "Column",
"workflow": [
{
"action": "Start",
"move_to": "Current",
"assign_to": "none"
}
],
"_t": "template_column"
},
{
"id": 40,
"name": "Current",
"position": 2,
"template_id": 4,
"column_type": "Column",
"workflow": [
{
"action": "Review",
"move_to": "Review",
"assign_to": "none"
}
],
"_t": "template_column"
},
{
"id": 41,
"name": "Review",
"position": 3,
"template_id": 4,
"column_type": "Column",
"workflow": [
{
"action": "Approve",
"move_to": "Approved",
"assign_to": "none"
}
],
"_t": "template_column"
},
{
"id": 42,
"name": "Approved",
"position": 4,
"template_id": 4,
"column_type": "Columns::Done",
"workflow": [],
"_t": "template_column"
}
]


I need to process this array and return an array with all workflow.action values. I have loadash available and I'm also transpiling the es6 code with babel.

I managed to do so, but I'm wondering if there's a more elegant way to do it:

My solution:

const workflow = _.map (
_.flatten(
_.map(template_columns, function(col) {
return col.workflow
})
), 'action'
)


So is there a better way to achieve what I'm looking for?

• Can you show example of desired result output? – Mike Brant Dec 14 '16 at 15:42
• – Tushar Dec 15 '16 at 5:34

1 Answer

You could use _.reduce() (or alternatively native Array.reduce()) to achieve this:

const workflow = _.reduce(
template_columns,
(colResult, colValue) => {
return colResult.concat(
_.reduce(
colValue.workflow,
(workflowResult, workflowValue) => workflowValue.action,
[]
);
);
},
[]
);


or

const workflow = template_columns.reduce(
(colResult, colValue) => {
return colResult.concat(
colValue.workflow.reduce(
(workflowResult, workflowValue) => workflowValue.action,
[]
);
);
},
[]
);


Note, use of arrow functions is optional, but since you indicated you are writing for ES6 and transpiling, and since the callback logic is so simple here, I thought the arrow function made sense.

Alternately, you might perform reduce then map like this:

const workflow = _.map(
_.reduce(
template_columns,
(result, value) => result.concat(value.workflow),
[]
),
val => val.action
);


This approach is actually similar to your current approach, using _reduce() rather than combination of _.flatten and _.map inside the outer _map() call.

Perhaps try it both ways (as well as your original approach) and see where you get best performance, based on the typical data you are working with.

• Thank you @Mike Brant. I like how short is your third solution. Which one do you think has more performance benefits? – Stafie Anatolie Dec 14 '16 at 17:07
• @StafieAnatolie It might depend on your typical and edge data cases (i.e. how many objects are in main array, how many workflow entries there are, etc.). That is why I mention testing. There are a number of ways to approach this problem as you have seen, now you should test with typical and edge data sets to see how each performs for you. If for example each object in main array only has single workflow step, perhaps my first solution works better, as it degenerates to an O(n) complexity case, whereas the second would be O(2n). – Mike Brant Dec 14 '16 at 18:05
• @StafieAnatolie Alternately, perhaps you test all of the data cases and you find you are splitting hairs performance-wise between solutions. At that point, you might simply opt for the one that you feel is more easily understandable/maintainable in your overall application context. – Mike Brant Dec 14 '16 at 18:10