简体   繁体   English

从包含文件路径的字符串列表创建树 - javascript

[英]Create a tree from a list of strings containing paths of files - javascript

Let's assume I have the following array:假设我有以下数组:

[
    "About.vue", 
    "Categories/Index.vue", 
    "Categories/Demo.vue", 
    "Categories/Flavors.vue"
]

We use the Index.vue in each sub-folder to act as the parent of that folder.我们使用每个子文件夹中的Index.vue作为该文件夹的父文件夹。 That means the above would look like:这意味着上面看起来像:

[
  { 
    name: "About", 
    children: [] 
  }, 
  { 
    name: "Categories", 
    children: 
    [
      {
        name: "Index.vue", 
        children: [] 
      },
      {
        name: "Demo.vue", 
        children: [] 
      },
      { 
        name: "Flavors.vue", 
        children: [] 
      }
    ]
  }
]

I was able to get it working slightly by using the following tutorial: https://joelgriffith.net/array-reduce-is-pretty-neat/通过使用以下教程,我能够让它稍微工作: https ://joelgriffith.net/array-reduce-is-pretty-neat/

However, the thing about that is that it is a root object with a property for each file, as opposed to an array with an object for each file.然而,它是一个根对象,每个文件都有一个属性,而不是一个数组,每个文件都有一个对象。

The following code produces the intended output:以下代码产生预期的输出:

 let paths = [ "About.vue", "Categories/Index.vue", "Categories/Demo.vue", "Categories/Flavors.vue" ]; let helper = { index: -1, name: "" }; function treeify(files) { var fileTree = []; function mergePathsIntoFileTree(prevDir, currDir, i, filePath) { helper.name = currDir; helper.index = i; if (helper.index == 0) { let index = prevDir.findIndex(x => x.name == helper.name); if (index < 0) { prevDir.push({ name: helper.name, children: [] }); } return prevDir; } if (helper.index >= 0) { let obj = { name: currDir, children: [] }; prevDir[helper.index].children.push(obj); helper.index = i; helper.name = currDir; } } function parseFilePath(filePath) { var fileLocation = filePath.split('/'); // If file is in root directory, eg 'index.js' if (fileLocation.length === 1) { fileTree[0] = { name: fileLocation[0], children: [] }; } else { fileLocation.reduce(mergePathsIntoFileTree, fileTree); } } files.forEach(parseFilePath); return fileTree; } console.log(treeify(paths));

However, it fails on the following input:但是,它在以下输入时失败:

let paths = [
    "About.vue", 
    "Categories/Index.vue", 
    "Categories/Demo.vue", 
    "Categories/Flavors.vue",
    "Categories/Types/Index.vue",
    "Categories/Types/Other.vue"
];

Does anyone know a solution to get it working for further nested lists of paths?有谁知道让它适用于进一步嵌套的路径列表的解决方案?

You can create this structure using forEach method to loop each path and split it to array on / , then you can also use reduce method to create nested objects.您可以使用forEach方法创建此结构以循环每个路径并将其拆分为/上的数组,然后您还可以使用reduce方法创建嵌套对象。

 let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue"]; let result = []; let level = {result}; paths.forEach(path => { path.split('/').reduce((r, name, i, a) => { if(!r[name]) { r[name] = {result: []}; r.result.push({name, children: r[name].result}) } return r[name]; }, level) }) console.log(result)

You could take an iterative approach for every found name part and get an object and return the children for the next search.您可以对每个找到的名称部分采用迭代方法并获取一个对象并返回子项以进行下一次搜索。

 var paths = ["About.vue", "Categories/Index.vue", "Categories/Demo.vue", "Categories/Flavors.vue", "Categories/Types/Index.vue", "Categories/Types/Other.vue"], result = paths.reduce((r, p) => { var names = p.split('/'); names.reduce((q, name) => { var temp = q.find(o => o.name === name); if (!temp) q.push(temp = { name, children: [] }); return temp.children; }, r); return r; }, []); console.log(result);
 .as-console-wrapper { max-height: 100% !important; top: 0; }

So, first off, I am going to assume this is in Node.js, second, I am currently at home so I don't have access to node.js at the moment so I had no real way of testing the code, however the following code should work.因此,首先,我将假设这是在 Node.js 中,其次,我目前在家,所以我目前无法访问 node.js,所以我没有真正的方法来测试代码,但是以下代码应该可以工作。

What you need to do is check the contents of the folder and then make a check to see if an item in the folder is a directory or not, if true, call the function again with the new path (aka recursion).您需要做的是检查文件夹的内容,然后检查文件夹中的项目是否为目录,如果为真,则使用新路径(也称为递归)再次调用该函数。

So first you start by reading the folder, add each item's name to the .name property of the object, then you check if it's a folder or not, if it is, recursive for that path.因此,首先您从读取文件夹开始,将每个项目的名称添加到对象的.name属性中,然后检查它是否是文件夹,如果是,则对该路径进行递归。 Keep returning an array of objects back (this will be added to the .children property.继续返回一个对象数组(这将被添加到.children属性中。

var fs = require('fs');

var filetree = DirToObjectArray('path/to/folder/');

function DirToObjectArray(path) {
        var arr = [];
    var content =  fs.readdirSync(path, { withFileTypes: true });
        for (var i=0; i< content.length; i++) {
        var obj = new Object({
        name: "",
        children: []
     });

     obj.name = content[i].name;

     if (content[i].isDirectory()) {
       obj.children = DirToObjectArray(path + content[i].name + "/");
     }

            arr.push(obj);
   }
   return arr;
}

If you are not using node.js but in-browser javascript, I can't help you with that如果您不使用 node.js 而是使用浏览器中的 javascript,我无法帮助您

I went with @Nenad Vracar 's answer (and upvoted, thank you!), but I also had the need to allow duplicate filenames in my use case.我接受了@Nenad Vracar回答(并赞成,谢谢!),但我也需要在我的用例中允许重复的文件名。 I just wanted to share how I did that.我只是想分享我是如何做到的。

 let paths = ["About.vue","Categories/Index.vue","Categories/Demo.vue","Categories/Flavors.vue","Categories/Types/Index.vue","Categories/Types/Other.vue","Categories/Types/Other.vue","Categories/Types/Other.vue"]; let result = []; let level = {result}; paths.forEach(path => { path.split('/').reduce((r, name, i, a) => { if(!r[name]) { r[name] = {result: []}; r.result.push({name, children: r[name].result}); } else if (i === a.length - 1) { // Allow duplicate filenames. // Filenames should always be at the end of the array. r.result.push({name, children: []}); } return r[name]; }, level) }) console.log(result)

The following solution was derived from @nenad-vracar's answer.以下解决方案源自@nenad-vracar 的回答。 One shortcoming with his answer is that if a path contains "result", the code will fail.他回答的一个缺点是,如果路径包含“结果”,则代码将失败。 A simple workaround would be to rename "result" to "", that is, include characters that cannot appear in a path.一个简单的解决方法是将“result”重命名为“”,即包含不能出现在路径中的字符。

export interface IPathNode {
  name: string;
  children: IPathNode[];
  path: IPath | null;
}

export interface IPath {
  key: string;
  directory: boolean;
}

interface IPathLevel {
  // ["<result>"]: IPathNode[];
  [key: string]: IPathLevel | IPathNode[];
}

export const createPathTree = (paths: IPath[]): IPathNode | null => {
  const level: IPathLevel = { ["<result>"]: [] as IPathNode[] };

  paths.forEach((path) => {
    path.key.split("/").reduce(
      ((
        currentLevel: IPathLevel,
        name: string,
        index: number,
        array: string[]
      ) => {
        if (!currentLevel[name]) {
          currentLevel[name] = { ["<result>"]: [] };
          (currentLevel["<result>"] as IPathNode[]).push({
            name,
            children: (currentLevel[name] as IPathLevel)[
              "<result>"
            ] as IPathNode[],
            /* Attach the path object to the leaf node. */
            path: index === array.length - 1 ? path : null,
          });
        }

        return currentLevel[name];
      }) as any,
      level
    );
  });

  const finalArray = level["<result>"] as IPathNode[];
  return finalArray.length > 0 ? finalArray[0] : null;
};

console.log(
  JSON.stringify(
    createPathTree([
      {
        key: "/components/button.tsx",
        directory: false,
      },
      {
        key: "/components/checkbox.tsx",
        directory: false,
      },
      {
        key: "/result",
        directory: true,
      },
    ]),
    null,
    4
  )
);

Output:输出:

{
    "name": "",
    "children": [
        {
            "name": "components",
            "children": [
                {
                    "name": "button.tsx",
                    "children": [],
                    "path": {
                        "key": "/components/button.tsx",
                        "directory": false
                    }
                },
                {
                    "name": "checkbox.tsx",
                    "children": [],
                    "path": {
                        "key": "/components/checkbox.tsx",
                        "directory": false
                    }
                }
            ],
            "path": null
        },
        {
            "name": "result",
            "children": [],
            "path": {
                "key": "/result",
                "directory": true
            }
        }
    ],
    "path": null
}

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

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