将文件/目录结构转换为JavaScript中的“树”

7

我有一个对象数组,看起来像这样:

[{ name: 'test',
  size: 0,
  type: 'directory',
  path: '/storage/test' },
{ name: 'asdf',
  size: 170,
  type: 'directory',
  path: '/storage/test/asdf' },
{ name: '2.txt',
  size: 0,
  type: 'file',
  path: '/storage/test/asdf/2.txt' }]

可能会有任意数量的任意路径,这是通过迭代目录中的文件和文件夹得到的结果。

我想要做的是确定这些路径的“根”节点。最终,这将被存储在mongodb中,并使用材料化路径来确定它们的关系。

在这个例子中,/storage/test 是没有父级的根目录。/storage/test/asdf 的父级是 /storage/test,而/storage/test/asdf/2.txt 则是/storage/test/asdf 的子级 。

我的问题是,如何遍历此数组以确定其父项和相关子项?希望能给予正确方向上的帮助!

谢谢


你是否正在寻找一种能够实际提供目录树结构的东西,其中/storage/test属于隐含节点/storage,而后者又属于隐含根目录/?我不完全确定你最终想要什么样的数据结构。 - Marcus Stade
澄清一下我的评论,如果你有一个文件在/storage/test和另一个在/storage/text/asdf/2.txt,那么/storage/text/asdf将被暗示,除非parent也可以表示grandparent。 - Marcus Stade
好的,我想基本上要有嵌套的子项,因此需要一个表示为“文件夹树”的JSON对象,如果这有意义的话。我想我必须通过递归迭代来保存它。 - dzm
3个回答

12

你可以这样做:

var arr = [] //your array;
var tree = {};

function addnode(obj){
  var splitpath = obj.path.replace(/^\/|\/$/g, "").split('/');
  var ptr = tree;
  for (i=0;i<splitpath.length;i++)
  {
    node = { name: splitpath[i],
    type: 'directory'};
    if(i == splitpath.length-1)
    {node.size = obj.size;node.type = obj.type;}
    ptr[splitpath[i]] = ptr[splitpath[i]]||node;
    ptr[splitpath[i]].children=ptr[splitpath[i]].children||{};
    ptr=ptr[splitpath[i]].children;
  }    
}

arr.map(addnode);
console.log(require('util').inspect(tree, {depth:null}));

输出

{ storage:
   { name: 'storage',
     type: 'directory',
     children:
      { test:
         { name: 'test',
           type: 'directory',
           size: 0,
           children:
            { asdf:
               { name: 'asdf',
                 type: 'directory',
                 size: 170,
                 children: { '2.txt': { name: '2.txt', type: 'file', size: 0, children: {} } } } } } } } }

3

假设文件列表中不会出现/,那么类似这样的代码将起作用:

function treeify(files) {
  var path = require('path')

  files = files.reduce(function(tree, f) {
    var dir = path.dirname(f.path)

    if (tree[dir]) {
      tree[dir].children.push(f)
    } else {
      tree[dir] = { implied: true, children: [f] }
    }

    if (tree[f.path]) {
      f.children = tree[f.path].children
    } else {
      f.children = []
    }

    return (tree[f.path] = f), tree
  }, {})

  return Object.keys(files).reduce(function(tree, f) {
    if (files[f].implied) {
      return tree.concat(files[f].children)
    }

    return tree
  }, [])
}

它将把你在问题中提到的数组转换成如下形式:
[ { name: 'test',
    size: 0,
    type: 'directory',
    path: '/storage/test',
    children: 
     [ { name: 'asdf',
         size: 170,
         type: 'directory',
         path: '/storage/test/asdf',
         children: 
          [ { name: '2.txt',
              size: 0,
              type: 'file',
              path: '/storage/test/asdf/2.txt',
              children: [] } ] } ] } ]

我实际上还没有用其他数据源测试过这个,所以你的结果可能会各不相同,但至少它应该会把你引向正确的方向。


1
这个代码可以运行,但是你能否在代码中添加一些注释吗?我有些地方不太理解。特别是在第一个reduce之后,除了其他所有内容之外,还有一个隐含的对象,然后你用第二个reduce把其他所有东西都丢掉了。这个过程不能在第一个reduce中完成吗? - Todd Horst
它缺少第一级目录“storage”。 - huan feng

2
基于@user568109的解决方案,但返回数组而不是对象结果:
function filesToTreeNodes(arr) {
  var tree = {}
  function addnode(obj) {
    var splitpath = obj.fileName.replace(/^\/|\/$/g, "").split('/');
    var ptr = tree;
    for (let i = 0; i < splitpath.length; i++) {
      let node: any = {
        fileName: splitpath[i],
        isDirectory: true
      };
      if (i == splitpath.length - 1) {
        node.isDirectory = false
      }
      ptr[splitpath[i]] = ptr[splitpath[i]] || node;
      ptr[splitpath[i]].children = ptr[splitpath[i]].children || {};
      ptr = ptr[splitpath[i]].children;
    }
  }
  function objectToArr(node) {
    Object.keys(node || {}).map((k) => {
      if (node[k].children) {
        objectToArr(node[k])
      }
    })
    if (node.children) {
      node.children = Object.values(node.children)
      node.children.forEach(objectToArr)
    }
  }
  arr.map(addnode);
  objectToArr(tree)
  return Object.values(tree)
}

这是更好地理解输入/输出格式的签名:
export interface TreeNode {
  isDirectory: string
  children: TreeNode[]
  fileName: string
}
export interface File {
  fileName: string
}
export type fileToTreeNodeType = (files: File[]) => TreeNode[]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接