deep flatten all items in collection using lodash
Asked Answered
M

5

9

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.

Metropolis answered 2/9, 2016 at 17:37 Comment(3)
Technically recursion is the functional approach...Collegiate
Your data is invalidScrunch
Recursion is nice-looking.Abrahan
N
12

There 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);
Needlepoint answered 2/9, 2016 at 18:50 Comment(2)
There is now _.flatMapDeep() which flattens collections recursively.Carrelli
@Carrelli it is not true that _.flatMapDeep() is only a short cut of _.map().flattenDeep() and do not recursively iterate any child nodeDiscommodity
L
3

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);
Lukash answered 26/9, 2021 at 14:23 Comment(0)
F
0

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.

Farinaceous answered 26/3, 2021 at 10:32 Comment(2)
You have a vanilla JS version and a lodash version using flattenDeep. I am not sure what flatMap would bring that the others haven't already covered on this 4 year old post.Granados
Acknowledged, officer @hppycoder! I will never post any new findings or ideas on old posts again.Farinaceous
P
0

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)
Pentomic answered 23/9, 2021 at 19:19 Comment(0)
E
-1

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.

Eczema answered 3/3, 2020 at 10:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.