12
\$\begingroup\$

I'm currently working on a small application, as a learning exercise for a Javascript novice. I need to generate an object based on a folder structure. Here is an example of the folder structure:

RootFolder/FolderA/FolderB/FileA

RootFolder/FolderA/FolderB/FileB

RootFolder/FolderA/FolderB/FolderC/FileA

RootFolder/FolderA/FolderB/FolderC/FileB

The desired resulting object should have the following structure:

{
    RootFolder: {
        FolderA: {
            FolderB: {
                FileA: {},
                FileB: {},
                FolderC: {
                    FileA: {},
                    FileB: {}
                }
            }
        }
    }
};

So far I came up with the following code and it seems to be doing the job, but I'm concerned with 'eval' use and generally believe there is a more elegant way of doing it.

module.exports = {
  structure: function(structureArray) {
    var generatedObject = {};
    for (var i = 0; i < structureArray.length; i++) {
      var objectElements = '';
      var path = (structureArray[i].split('/')).filter(Boolean);
      for (var l = 0; l < path.length; l++) {
        if (objectElements.indexOf(path[l]) == -1 || path[l].length == 1) {
          objectElements += '["' + path[l] + '"]';
        }
        if (!(eval('generatedObject' + objectElements))) {
          eval('generatedObject' + objectElements + '= {}')
        }
      }
    }
    return generatedObject;
  }
};

structureArray contains full path strings as in the folder structure example above (e.g. 'RootFolder/FolderA/FolderB/FileA')

\$\endgroup\$
1
  • \$\begingroup\$ I would use paths delimited with dot . like this : ['RootFolder/FolderA/FolderB/FileA','RootFolder/FolderA/FolderB/FileB', '...'] and use _.set lodash.com/docs/4.17.4#set \$\endgroup\$
    – Jan Grz
    Commented Mar 17, 2017 at 14:29

2 Answers 2

13
\$\begingroup\$

You can split each path at / using split and then use reduce() to build that object.

var paths = [
  'RootFolder/FolderA/FolderB/FileA',
  'RootFolder/FolderA/FolderB/FileB',
  'RootFolder/FolderA/FolderB/FolderC/FileA',
  'RootFolder/FolderA/FolderB/FolderC/FileB'
]

var obj = {}
paths.forEach(function(path) {
  path.split('/').reduce(function(r, e) {
    return r[e] || (r[e] = {})
  }, obj)
})

console.log(obj)

Here is how you can use this approach to read some directory and all of its nested content and create object from that. This approach is synchronous.

var fs = require('fs');
var path = require('path');

var p = path.resolve('C:\\your\\path\\here')
var result = {}

function buildObject(startPath) {
  var dir = fs.readdirSync(startPath)
  dir.forEach(function(e) {
    var newPath = path.join(startPath, e)

    var stat = fs.statSync(newPath);
    if (stat && stat.isDirectory()) buildObject(newPath)
    if (stat.isFile()) {
      newPath.split('\\').reduce(function(r, a) {
        return r[a] || (r[a] = {})
      }, result)
    }
  })
}

buildObject(p)
console.log(result)
\$\endgroup\$
3
  • \$\begingroup\$ sorry my ignorance, but how could remove the "file.jpg": {}, when it is a file \$\endgroup\$
    – Vitor Hugo
    Commented Sep 22, 2020 at 22:25
  • 1
    \$\begingroup\$ @VitorHugo You can replace the return line with this return r[e] || (e.indexOf(".")==-1?(r[e] = {}):r[e]="") but beware, you'll have to handle this while reading. \$\endgroup\$ Commented Oct 4, 2020 at 6:54
  • \$\begingroup\$ @ShivamSharma no and what I'm looking for, your solution just exchanged brackets for quotes, what I'm trying to and remove the brackets from the files, convert the files into simple value, thanks for trying to help me. - prnt.sc/usvfyh \$\endgroup\$
    – Vitor Hugo
    Commented Oct 4, 2020 at 12:12
5
\$\begingroup\$

Although the previous answer does solve OP's needs, I believe an asynchronous non-blocking approach would help making the code more concise and easy to use, whilst also avoiding explicit calls to stats.

We can use ES6 async generators to walk (read) our directories:

import { join } from 'path'
import { opendir } from 'fs/promises'

async function* walk(root) {
  for await (const entry of await opendir(root)) {
    const path = join(root, entry.name)
    if (entry.isDirectory()) yield* await walk(path)
    else if (entry.isFile()) yield path
  }
}

Then iterate over them like so:

for await (const path of walk('path/to/dir')) {
  console.log(path)
}

We'd then be able to build our tree using an IIFE (Immediately Invoked Function Expression) inside a .js file.

;(async () => {
  let tree = {}
  for await (const path of walk('../../my-dir')) {
    path.split('/').reduce((o, k) => (o[k] = o[k] ||  {}), tree)
  }
  console.log(JSON.stringify(tree, null, 2))
})()
\$\endgroup\$
0

Your Answer

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