I'm looking to flatten an array that look like this:
[{
"id": 0,
"text": "item 0"
}, {
"id": 1,
"items": [{
"id": 2,
"text": "item 2"
}, {
"id": 3,
"items": [{
"id": 4,
"text": "item 4"
}]
}]
}]
into this
[{
"id": 0,
"text": "item 0"
}, {
"id": 2,
"text": "item 2"
}, {
"id": 4,
"text": "item 4"
}]
basically keeping all element that doesn't have an "items" property, and if they have one, recursively traverse all "items" arrays deep.
I could indeed write a recursive function, but I'm looking for a nice looking lodash or underscore way to solve this.
I'm looking to flatten an array that look like this:
[{
"id": 0,
"text": "item 0"
}, {
"id": 1,
"items": [{
"id": 2,
"text": "item 2"
}, {
"id": 3,
"items": [{
"id": 4,
"text": "item 4"
}]
}]
}]
into this
[{
"id": 0,
"text": "item 0"
}, {
"id": 2,
"text": "item 2"
}, {
"id": 4,
"text": "item 4"
}]
basically keeping all element that doesn't have an "items" property, and if they have one, recursively traverse all "items" arrays deep.
I could indeed write a recursive function, but I'm looking for a nice looking lodash or underscore way to solve this.
Share Improve this question edited Sep 2, 2016 at 17:52 nox asked Sep 2, 2016 at 17:37 noxnox 3511 gold badge4 silver badges13 bronze badges 3- 1 Technically recursion is the functional approach... – Joseph Young Commented Sep 2, 2016 at 17:39
- Your data is invalid – Nenad Vracar Commented Sep 2, 2016 at 17:40
- Recursion is nice-looking. – user663031 Commented Sep 2, 2016 at 18:47
5 Answers
Reset to default 12There is no neat function for this in lodash or underscore. I think a recursive function is your best bet:
function collect(array, result) {
array.forEach(function(el) {
if(el.items) {
collect(el.items, result);
} else {
result.push(el);
}
});
}
var array = [{
"id": 0,
"text": "item 0"
}, {
"id": 1,
"items": [{
"id": 2,
"text": "item 2"
}, {
"id": 3,
"items": [{
"id": 4,
"text": "item 4"
}]
}]
}];
function collect(array, result) {
array.forEach(function(el) {
if(el.items) {
collect(el.items, result);
} else {
result.push(el);
}
});
}
var result = [];
collect(array, result);
console.log(result);
You can also use the native flatMap function:
const tree = [{
"id": 0,
"text": "item 0"
}, {
"id": 1,
"items": [{
"id": 2,
"text": "item 2"
}, {
"id": 3,
"items": [{
"id": 4,
"text": "item 4"
}]
}]
}]
function flattenTree(tree){
return tree.flatMap( item => item.items ? [item, ...flattenTree(item.items)] : item);
}
flattenTree(tree);
If you want remove the prop "items" you can do this:
const tree = [{
"id": 0,
"text": "item 0"
}, {
"id": 1,
"items": [{
"id": 2,
"text": "item 2"
}, {
"id": 3,
"items": [{
"id": 4,
"text": "item 4"
}]
}]
}]
function flattenTreeNoItems(tree){
return tree.flatMap( item => {
if(item.items){
const items = item.items;
delete item.items;
return [item, ...flattenTreeNoItems(items)];
}
return item;
});
}
flattenTreeNoItems(tree);
Came across this today and surprised that lodash flatMapDeep
doesnt work for this. Here's a simple recursive function I wrote that does what you would expect and also allows you to map at the same time.
function flattenTree(items, callback, nestingKey = 'items') {
let output = []
items.forEach(item => {
output.push(callback ? callback(item) : item)
output = output.concat(flattenTree(item[nestingKey] || [], callback, nestingKey))
})
return output
}
- 1st parameter is your tree structure
- 2nd parameter is an optional callback function to control how you want the results mapped
- 3rd parameter can be used if your nesting key in the tree is something other than
items
Example usage for your use-case:
let output = flattenTree(items, item => ({
id: item.id,
text: item.text
}))
Example to pull out just the ID
field:
let ids = flattenTree(items, item => item.id)
Possible solution in 2 lines of code, using lodash/flatMap:
const iteratee = item => (item.items ? _.flatMap(item.items, iteratee) : item);
const flattenedItems = _.flatMap(sourceItems, iteratee);
Written off the top of my head, so take it with a grain of salt.
lodash/flattenDeep will recursively flatten an array. E.g.:
import {flattenDeep} from 'lodash'
const nestedArray = [1, ['2', [3, [{x: 4}]]]]
const mixedNestedArray = [1, ['2', [3, [{x: [[4]]}]]]]
console.log(flattenDeep(nestedArray)) // [1, '2', 3, {x: 4}]
console.log(flattenDeep(mixedNestedArray)) // [1, '2', 3, {x: [[4]]}]
Note that a nested array inside an object won't be affected, which is what you'd expect.