Construct flat array from tree of objects

This function will do the job, plus it adds a level indicator to each object. Immediate children of treeObj will be level 1, their children will be level 2, etc. The parent properties are updated as well.

function flatten(treeObj, idAttr, parentAttr, childrenAttr, levelAttr) {
    if (!idAttr) idAttr = 'id';
    if (!parentAttr) parentAttr = 'parent';
    if (!childrenAttr) childrenAttr = 'children';
    if (!levelAttr) levelAttr = 'level';

    function flattenChild(childObj, parentId, level) {
        var array = []; 

        var childCopy = angular.extend({}, childObj);
        childCopy[levelAttr] = level;
        childCopy[parentAttr] = parentId;
        delete childCopy[childrenAttr];
        array.push(childCopy);

        array = array.concat(processChildren(childObj, level));

        return array;
    };

    function processChildren(obj, level) {
        if (!level) level = 0;
        var array = [];

        obj[childrenAttr].forEach(function(childObj) {
            array = array.concat(flattenChild(childObj, obj[idAttr], level+1));
        });

        return array;
    };

    var result = processChildren(treeObj);
    return result;
};

This solution takes advantage of Angular's angular.extend() function to perform a copy of the child object. Wiring this up with any other library's equivalent method or a native function should be a trivial change.

The output given for the above example would be:

[{
    "id": 1,
    "title": "home",
    "parent": null,
    "level": 1
}, {
    "id": 2,
    "title": "about",
    "parent": null,
    "level": 1
}, {
    "id": 3,
    "title": "team",
    "parent": 2,
    "level": 2
}, {
    "id": 4,
    "title": "company",
    "parent": 2,
    "level": 2
}]

It is also worth noting that this function does not guarantee the array will be ordered by id; it will be based on the order in which the individual objects were encountered during the operation.

Fiddle!


Hope your are familiar with es6:

let flatten = (children, extractChildren) => Array.prototype.concat.apply(
  children, 
  children.map(x => flatten(extractChildren(x) || [], extractChildren))
);

let extractChildren = x => x.children;

let flat = flatten(extractChildren(treeStructure), extractChildren)
               .map(x => delete x.children && x);

UPD:

Sorry, haven't noticed that you need to set parent and level. Please find the new function below:

let flatten = (children, getChildren, level, parent) => Array.prototype.concat.apply(
  children.map(x => ({ ...x, level: level || 1, parent: parent || null })), 
  children.map(x => flatten(getChildren(x) || [], getChildren, (level || 1) + 1, x.id))
);

https://jsbin.com/socono/edit?js,console