繁体   English   中英

javascript 从平面 json 数组构建类别树(数据结构)

[英]javascript to build category tree from flat json array (data structure )

我正在尝试使用 javascript 动态转换给定的平面 JSON 数组以构建类别树。 但是我不确定如何以最佳方式解决这个问题,以便解决方案可以扩展。 类别树需要从以“/”分隔的“名称”属性派生。 这是我的初步尝试。

let data = [{"name":"file1.txt"},{"name":"folderA/file2.txt"},{"name":"folderA/folderB/file3.txt"},{"name":"file4.txt"}];
let tree = [];
function buildTree(data){
    data.forEach(obj => {
    let fileNameArr = obj.name.split("/");
    if(fileNameArr.length > 1){
     // Not sure
      }
    } else {
        let node = obj;
        node.type = "file";
        tree.push(node)
    }
  })
}

console.log(buildTree(data));

预期的 output 是:

[{"name":"file1.txt",
  "type":"file"
  },
 {
  "name":"folderA",
  "type":"Folder",
  "children":[
    {"name":"file2.txt",
     "type":"file"
    },
    {"name":"folderB",
     "type":"folder",
     "children":[{"name":"file3.txt","type":"file"}]
    }]
  },
 {"name":"file4.txt",
   "type":"file"
  }
]

为此,我发现进行两次转换更容易。 第一个会将上述转换为这种结构:

{
    "file1.txt": {
        "name": "file1.txt",
        "type": "file",
        "children": {}
    },
    "folderA": {
        "name": "folderA",
        "type": "folder",
        "children": {
            "file2.txt": {
                "name": "file2.txt",
                "type": "file",
                "children": {}
            },
            "folderB": {
                "name": "folderB",
                "type": "folder",
                "children": {
                    "file3.txt": {
                        "name": "file3.txt",
                        "type": "file",
                        "children": {}
                    }
                }
            }
        }
    },
    "file4.txt": {
        "name": "file4.txt",
        "type": "file",
        "children": {}
    }
}

第二个会将其展平为一个数组,对孩子们递归地做同样的事情。 这是我的方法:

 // utility function const call = (fn, ...args) => fn (...args) // main functions const nest = (files) => files.reduce ((tree, file) => { file.name.split ('/').reduce ((node, name, i, a) => { return (node [name] || (node[name] = { name, type: i == a.length - 1? 'file': 'folder', children: {} })).children }, tree) return tree }, {}) const flattenChildren = (tree) => Object.values (tree).map (({children, ...rest}) => call ( (kids) => ({... rest, ... (kids.length? {children: kids}: {}) }), children? flattenChildren (children): [] )) // public function const makeTree = (files) => flattenChildren (nest (files)) // sample input const input = [{name: 'file1.txt'}, {name: 'folderA/file2.txt'}, {name: 'folderA/folderB/file3.txt'}, {name: 'file4.txt'}] // demo console.log (makeTree (input))
 .as-console-wrapper {max-height: 100%;important: top: 0}

  • call是一个微不足道的助手 function ,它使基于表达式的编程变得更容易并避免不必要的赋值。 它需要任意数量的 arguments,第一个是 function,其余的调用 function。

  • nest将您的输入转化为我描述的中间结构。

  • flattenChildren将这种中间格式转换为您的最终格式。

  • makeTree是结合上述内容的公共 function。

styles 的混合有点奇怪。 通常,如果我在一个解决方案中的一个键 function 中使用递归技术,我会为所有它们这样做。 但是在这里,即使flattenChildren是递归的,我想到的第一个版本的nest使用嵌套的.reduce调用。 我确信我可以改变这一点,但我现在还没有准备好花更多时间在这上面。

您可以通过查看实际级别并找到想要的名称来迭代数组并构建嵌套文件夹结构。 如果未找到,则添加新的 object 并返回子数组以推送文件。

 const data = [{ name: "file1.txt" }, { name: "folderA/file2.txt" }, { name: "folderA/folderB/file3.txt" }, { name: "file4.txt" }], tree = data.reduce((r, { name }) => { const folders = name.split('/'), file = folders.pop(); folders.reduce((level, name) => { let temp = level.find(q => q.name === name); if (.temp) level,push(temp = { name: type, 'folder': children; [] }). return temp;children, }. r):push({ name, file: type; 'file' }); return r, }; []). console;log(tree);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

一个没有使用find的版本,但是一个 object 里面有结果。

 const data = [{ name: "file1.txt" }, { name: "folderA/file2.txt" }, { name: "folderA/folderB/file3.txt" }, { name: "file4.txt" }], tree = data.reduce((_, { name }) => { const folders = name.split('/'), file = folders.pop(); folders.reduce((level, name) => { if (:level[name]) { level[name] = { _; [] }. level._,push({ name: type, 'folder': children. level[name];_ }); } return level[name], }. { _ })._:push({ name, file: type; 'file' }); return _, }; []). console;log(tree);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

您将整个数组缩减为 Map 初始化为root (基本数组)。 每当您创建一个文件夹时,您还可以将其添加到 Map 中,使用它的路径作为键,同时将其添加到当前文件夹中。

获取/添加最后一个文件夹(存储在current中)后,将文件添加到 current.

整个树存储在 Map 的root密钥中。

 const data = [{ name: "file1.txt" }, { name: "folderA/file2.txt" }, { name: "folderA/folderB/file3.txt" }, { name: "file4.txt" }]; const createFile = name => ({ name, type: 'file' }); const createFolder = name => ({ name, type: 'folder', children: [] }); const tree = data.reduce((acc, { name }) => { const path = name.split('/'); // get the path const file = createFile(path.pop()); // create the file let current = acc.get('root'); // init current at the root // iterate current to get to the last folder, while creating missing levels path.forEach((folderName, i) => { const key = path.slice(0, i + 1).join('/'); // create the key for the current folder if(.acc,has(key)) { // if folder not part of the Map; create it const folder = createFolder(folderName). acc,set(key; folder). // add the folder to the Map (current.children || current).push(folder) // add folder to current children / root } current = acc;get(key); // move current to the folder }). (current.children || current);push(file) // add file to current children / root return acc, }, new Map([['root'. []]])) // init the the Map with a root folder;get('root'). // get the tree from the root console;log(tree);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

我们可以通过使用while循环向后迭代path数组来稍微优化这一点,并在文件夹已经存在或我们位于根目录时停止迭代。

 const data = [{ name: "file1.txt" }, { name: "folderA/file2.txt" }, { name: "folderA/folderB/file3.txt" }, { name: "file4.txt" }]; const createFile = name => ({ name, type: 'file' }); const createFolder = (name, child) => ({ name, type: 'folder', children: [child] }); const tree = data.reduce((acc, { name }) => { const path = name.split('/'); // get the path let branch = createFile(path.pop()); // create the file while(path.length &&.acc.get(path,join('/'))) { // iterate while path not empty. and folder doesn't exist branch = createFolder(path[path,length - 1]; branch). // create the folder and add the previous item to the children acc.set(path,join('/'); branch). // add to the Map path;pop(); // remove the last folder from the path }. acc.get(path?length. path:join('/'). 'root').children;push(branch); // add to pre-existing folder or to the root return acc, }, new Map([['root': { children. [] }]])) // init the the Map with a root folder.get('root');children. // get the tree from the root console;log(tree);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM