从对象树构造平面数组

问题描述 投票:0回答:9

假设我有一棵如下所示的对象树,可能是使用此处找到的优秀算法创建的: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": []
        }]
    }]
}

(具体来说,在本示例中,该函数返回的数组作为

children
数组属性嵌套在一个空对象内。)

如何将其转换回平面数组?

javascript arrays algorithm tree hierarchy
9个回答
19
投票

希望你熟悉 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);

更新:

抱歉,没有注意到您需要设置父级和级别。请在下面找到新功能:

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,控制台


5
投票

由于新答案再次提出了这个问题,因此值得考虑一种现代的简单方法:

const flatten = ({children}) =>
  children .flatMap (({children = [], ...rest}) => [rest, ...flatten ({children})])

let tree = {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: []}]}]}

console .log (flatten (tree))
.as-console-wrapper {max-height: 100% !important; top: 0}

使用

Array.prototype.flatMap
,我们将项目映射到平面数组中,并在其
children
属性上重复出现。


4
投票

这个函数可以完成这项工作,而且它还为每个对象添加了一个级别指示器。 treeObj 的直接子级将是级别 1,其子级将是级别 2,依此类推。

parent
属性也会更新。

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;
};

此解决方案利用 Angular 的

angular.extend()
函数来执行子对象的复制。将其与任何其他库的等效方法或本机函数连接应该是一个微不足道的更改。

上述示例的输出为:

[{
    "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
}]

还值得注意的是,该函数不保证数组将按

id
排序;它将基于操作期间遇到各个对象的顺序。

小提琴!


3
投票

尝试遵循此方法仅假设每个项目都有子属性

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;
 }
}

使用方法

 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], []);

2
投票

这是我的贡献:

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]);
    }

1
投票

这是数据:

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

在 React.JS 中,只需在 state 中声明一个数组字段并将项目推送到该数组。

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

函数调用后,您的数组状态将保存所有项目,如下所示:


0
投票

再来一个😄😁

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"
    }
];

0
投票

你可以使用这样的递归函数:

toFlat(items) {
  if(!items || !items.length) {
     return []
  }
  return items.reduce((totalItems, item) => {
    totalItems.push(item)
    return totalItems.concat(toFlat(item.children))
  }, [])
}

0
投票

由于最近恢复了此功能,因此这里有一个使用 object-scan 的解决方案。使用记录良好且灵活的库应该可以更轻松地根据需要调整功能

.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/[email protected]/lib/index.min.js';

const obj = { 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: [] }] }] };

const r = objectScan(['**{children[*]}'], {
  rtn: 'value',
  afterFn: ({ result }) => result.map(({ children, ...rest }) => rest)
})(obj);

console.log(r);
/* => [
  { id: 4, title: 'company', parent: 2 },
  { id: 3, title: 'team', parent: 2 },
  { id: 2, title: 'about', parent: null },
  { id: 1, title: 'home', parent: null }
] */
</script>

免责声明:我是object-scan

的作者

免责声明:我是对象扫描的作者

© www.soinside.com 2019 - 2024. All rights reserved.