4

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.

4
  • Questions that imply having us write it for you are frowned upon, Take a look at the .split() method of javascript strings, and get to know recursion. Take a shot and ask for help if you run into trouble. Commented Oct 31, 2018 at 15:32
  • This is my current code. but it is not quite there, Commented Oct 31, 2018 at 15:35
  • Great. Can you put your current solution in the question, and describe what's not working? Commented Oct 31, 2018 at 15:42
  • already did. check. Commented Oct 31, 2018 at 15:45

2 Answers 2

5

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)

Sign up to request clarification or add additional context in comments.

1 Comment

yap, your code works. even if I go a level deeper or two. I am marking it as the correct answer. thank you!
4

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

2 Comments

Yap your code works but. it started to misbehave if I go a level deeper.
Not sure what you mean. It works for me. Added test input/output to demonstrate

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.