12

Suppose I have a tree of objects like the following, perhaps created using the excellent algorithm found here: https://stackoverflow.com/a/22367819/3123195

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

(Specifically in this example, the array returned by that function is nested as the children array property inside an otherwise empty object.)

How would I convert it back to a flat array?

Community
  • 1
  • 1
The DIMM Reaper
  • 3,388
  • 3
  • 28
  • 45
  • I found plenty of questions asking how to create a tree from an array, but none converting back in the other direction, so I've posted the solution I came up with. – The DIMM Reaper Sep 16 '15 at 12:59

6 Answers6

13

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

slowkot
  • 398
  • 2
  • 11
  • Wow, I can't believe how little code that was, and it works perfectly. ES6 is great! Oh, and the level indicator wasn't necessary, I just added it to my implementation for illustration purposes. – The DIMM Reaper Jun 29 '17 at 17:10
  • Yeah, es6 and next are inspiring! Glad to read this! In addition, so far the first solution will be valid es5 if we replace lambdas with anonymous functions. – slowkot Jun 29 '17 at 20:45
4

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!

The DIMM Reaper
  • 3,388
  • 3
  • 28
  • 45
  • 1
    You should call your function `flattenChildrenOf` or something similar since it's disregarding the root at all times. Nothing says the root couldn't be an important node. – plalx Sep 16 '15 at 13:33
  • Keep in mind that the above answer is recursive, and hence could be improved. Since I could not find a npm module which implements a O(n) solution, I created the following one (unit tested, 100% code coverage, only 0.5 kb in size and includes typings. Maybe it helps someone: npmjs.com/package/performant-array-to-tree – Philip Stanislaus May 07 '17 at 17:37
2

Here it goes my contribution:

function flatNestedList(nestedList, childrenName, parentPropertyName, idName, newFlatList, parentId) {

        if (newFlatList.length === 0)
            newFlatList = [];

        $.each(nestedList, function (i, item) {
            item[parentPropertyName] = parentId;
            newFlatList.push(item);
            if (item[childrenName] && item[childrenName].length > 0) {
                //each level
                flatNestedList(item[childrenName], childrenName, parentPropertyName, idName, newFlatList, item[idName]);
            }
        });

        for (var i in newFlatList)
            delete (newFlatList[i][childrenName]);
    }
Candous
  • 143
  • 3
  • 7
0

Try following this only assumes each item is having children property

class TreeStructureHelper {
   public toArray(nodes: any[], arr: any[]) {
    if (!nodes) {
      return [];
    }
    if (!arr) {
      arr = [];
    }
    for (var i = 0; i < nodes.length; i++) {
      arr.push(nodes[i]);
      this.toArray(nodes[i].children, arr);
    }
  return arr;
 }
}

Usage

 let treeNode =
 {
   children: [{
    id: 1,
    title: "home",
    parent: null,
    children: []
   }, {
    id: 2,
    title: "about",
    parent: null,
    children: [{
        id: 3,
        title: "team",
        parent: 2,
        children: []
     }, {
        id: 4,
        title: "company",
        parent: 2,
        children: []
     }]
   }]
 };
 let flattenArray = _treeStructureHelper.toArray([treeNode], []);
Hemant
  • 178
  • 5
  • 11
0

This is data:

 const data = {
      id: '1',
      children: [
        {
          id: '2',
          children: [
            {
              id: '4',
              children: [
                {
                  id: '5'
                },
                {
                  id: '6'
                }
              ]
            },
            {
              id: '7'
            }
          ]
        },
        {
          id: '3',
          children: [
            {
              id: '8'
            },
            {
              id: '9'
            }
          ]
        }
      ]
    }

In React.JS just declare an array field in state and push items to that array.

  const getAllItemsPerChildren = item => {
    array.push(item);
    if (item.children) {
      return item.children.map(i => getAllItemsPerChildren(i));
    }
  }

After function call your array in state will hold all items as below:

enter image description here

Rufat Gulabli
  • 572
  • 5
  • 10
0

One more

function flatten(root, parent=null, depth=0, key='id', flat=[], pick=() => {}) {
    flat.push({
        parent,
        [key]: root[key],
        depth: depth++,
        ...pick(root, parent, depth, key, flat)
    });
    
    if(Array.isArray(root.children)) {
        root.children.forEach(child => flatten(child, root[key], depth, key, flat, pick));
    }
}

let sample = {
    "id": 0,
    "children": [{
        "id": 1,
        "title": "home",
        "parent": null,
        "children": []
    }, {
        "id": 2,
        "title": "about",
        "parent": null,
        "children": [{
            "id": 3,
            "title": "team",
            "parent": 2,
            "children": []
        }, {
            "id": 4,
            "title": "company",
            "parent": 2,
            "children": []
        }]
    }]
};

let flat = [];

flatten(sample, null, 0, 'id', flat, root => ({ title: root.title }));

let expected = [
    {
        "id": 0,
        "parent": null,
        "depth": 0
    },
    {
        "id": 1,
        "parent": 0,
        "depth": 1,
        "title": "home"
    },
    {
        "id": 2,
        "parent": 0,
        "depth": 1,
        "title": "about"
    },
    {
        "id": 3,
        "parent": 2,
        "depth": 2,
        "title": "team"
    },
    {
        "id": 4,
        "parent": 2,
        "depth": 2,
        "title": "company"
    }
];
wmik
  • 624
  • 10
  • 22