Reputation: 456
I need a javascript function to turn an array with file path string to object as follows:
let files = [
"Folder/file.ext",
"Folder/file2.ext",
"Folder/file3.ext",
"Folder/nestedfolder/file.ext",
"Folder2/file1.ext",
"Folder2/file2.ext",
"file1.ext",
"file2.ext",
"file3.ext",
];
listToTree(files);
And It should output an array with an object as follows:
[
{
text: "Folder",
children: [
{text: "file.ext"},
{text: "file1.ext"},
{text: "file2.ext"},
{text: "nestedfolder", children: [{text: "file.ext"}]},
]
},
{
text: "Folder2",
children: [
{text: "file1.ext"},
{text: "file2.ext"},
]
},
{text: "file1.ext"},
{text: "file2.ext"},
{text: "file3.ext"}
];
Here is the current function I am using. but it is not quite there.
function listToTree(files) {
let filestmp = files.map(file => {
if (typeof file === "string") return file;
return file.path
});
let filesl = filestmp.map(fileee => fileToObject(fileee));
return filesl;
}
function fileToObject(filee) {
if (filee.includes("/")) {
// this is a folder
let count = filee.indexOf("/");
return {text: filee.substring(0, count), children: [fileToObject(filee.substring(count + 1))]}
} else {
// this is a file
return {text: filee}
}
}
export default listToTree
it outputs:
[ { text: 'Folder', children: [ { text: 'file.ext' } ] },
{ text: 'Folder', children: [ { text: 'file2.ext' } ] },
{ text: 'Folder', children: [ { text: 'file3.ext' } ] },
{ text: 'Folder',
children:
[ { text: 'nestedfolder', children: [ { text: 'file.ext' } ] } ] },
{ text: 'Folder2', children: [ { text: 'file1.ext' } ] },
{ text: 'Folder2', children: [ { text: 'file2.ext' } ] },
{ text: 'file1.ext' },
{ text: 'file2.ext' },
{ text: 'file3.ext' } ]
now as you can see. each file list array getting its own object. I need to combine the files located in the same folder location.
Upvotes: 4
Views: 4099
Reputation: 4694
Here's my take, one function, no recursion:
const listToTree = files =>
files.map(file => file.split('/'))
.reduce((out, path) => {
let top = out;
while (path.length > 0) {
let node = path.shift();
if (top.findIndex(n => n.text === node) === -1) {
top.push({
text: node
});
}
if (path.length > 0) {
let index = top.findIndex(n => n.text === node);
top[index] = top[index] || {};
top[index].children = top[index].children || [];
top[index].children.push({
text: path[0]
});
top = top[index].children;
}
}
return out;
}, []);
let files = [
'Folder/file.ext',
'Folder/file2.ext',
'Folder/file3.ext',
'Folder/nestedfolder/file.ext',
'Folder2/nestedfolder1/nestedfolder2/file1.ext',
'Folder2/file2.ext',
'file1.ext',
'file2.ext',
'file3.ext'
];
console.log(listToTree(files));
Upvotes: 4
Reputation: 92440
Having a tree represented as an array is a little inconvenient because you need to search the array each time to find the appropriate node, which would be inefficient for large arrays. On option is to just build a tree object in one pass and then do a second pass to just take the Object.values
. Here's an example of that:
let files = ["Folder/file.ext","Folder/file2.ext","Folder/file3.ext","Folder/nestedfolder/file.ext","Folder2/file1.ext","Folder2/file2.ext","file1.ext","file2.ext","file3.ext",];
function addPath(arr, obj = {}){
let component = arr.shift()
let current = obj[component] || (obj[component] = {text:component})
if (arr.length) {
addPath(arr, current.children || (current.children = {}))
}
return obj
}
function makeArray(obj){
let arr = Object.values(obj)
arr.filter(item => item.children).forEach(item => {
item.children = makeArray(item.children)
})
return arr
}
// make tree
let treeObj = files.reduce((obj, path) => addPath(path.split('/'), obj), {})
// convert to array
let arr = makeArray(treeObj)
console.log(arr)
An alternative is using find()
which will work and may be easier to read…but may be less efficient because you need to search the result array on each pass:
let files = ["Folder/file.ext","Folder/file2.ext","Folder/file3.ext","Folder/nestedfolder/file.ext","Folder2/file1.ext","Folder2/file2.ext","file1.ext","file2.ext","file3.ext",];
function addPath(pathcomponents, arr ){
let component = pathcomponents.shift()
let comp = arr.find(item => item.text === component)
if (!comp) {
comp = {text: component}
arr.push(comp)
}
if(pathcomponents.length){
addPath(pathcomponents, comp.children || (comp.children = []))
}
return arr
}
let res = files.reduce((arr, path) => addPath(path.split('/'), arr), [])
console.log(res)
Upvotes: 5