Bubun
Bubun

Reputation: 456

Turn an array with file path to object as mentioned

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

Answers (2)

Jim B.
Jim B.

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

Mark
Mark

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

Related Questions