Home > Software design >  How to create nested object directory name till it found a file
How to create nested object directory name till it found a file

Time:10-08

I'm making a nested object that contains a file directory and file name if don't have any directory left

expected :

{ dir_name : "test",
  sub_dir : {dir_name : "test2",
             sub_dir : {dir_name : "test3".
                        sub_dir: [0001.dcm, 0002.dcm, 003.dcm]}}
}

my result:

{ dir_name: 'test', sub_dir: Promise { <pending> } }

my code:

async function make_dir(FilePath){
  return new Promise((resolve, reject) => {
    fs.readdir(FilePath, async (err,files) => {
      var arr = [];
      files.forEach((file) => {
        if (!file.includes(".dcm")){
          var container = { dir_name: file,
                        sub_dir: {}}
          container.sub_dir =  make_dir(FilePath   file   '/')
          resolve(container)
        }
        else{
          arr.push(file);
        }
      });
      resolve(arr)
    })
  })
}

CodePudding user response:

The proposed structure is invalid as a directory can have one or more subdirectories -

{ 
  dir: "foo",
  sub_dir: { ... },
  sub_dir: { ... }, // cannot reuse "sub_dir" key
}

Here's an alternative that will build a tree like this -

{ 
  "test1":
    {
      "test2":
         {
            "test3":
              {    
                "0001.dcm": true,
                "0002.dcm": true,
                "003.dcm": true
              }
         }
    }
}

fs/promises and fs.Dirent

Here's an efficient, non-blocking program using Node's fast fs.Dirent objects and fs/promises module. This approach allows you to skip wasteful fs.exist or fs.stat calls on every path -

// main.js

import { readdir } from "fs/promises"
import { join, basename } from "path"

async function* tokenise (path = ".")
{ yield { dir: path }
  for (const dirent of await readdir(path, { withFileTypes: true }))
    if (dirent.isDirectory())
      yield* tokenise(join(path, dirent.name))
    else
      yield { file: join(path, dirent.name) }
  yield { endDir: path }
}

async function parse (iter = empty())
{ const r = [{}]
  for await (const e of iter)
    if (e.dir)
      r.unshift({})
    else if (e.file)
      r[0][basename(e.file)] = true
    else if (e.endDir)
      r[1][basename(e.endDir)] = r.shift()
  return r[0]
}

async function* empty () {}

Now createTree is simply a combination of tokenise and parse -

const createTree = (path = ".") =>
  parse(tokenise(path))

createTree(".")
  .then(r => console.log(JSON.stringify(r, null, 2)))
  .catch(console.error)

Let's get some sample files so we can see it working -

$ yarn add immutable     # (just some example package)
$ node main.js
{
  ".": {
    "main.js": true,
    "node_modules": {
      ".yarn-integrity": true,
      "immutable": {
        "LICENSE": true,
        "README.md": true,
        "contrib": {
          "cursor": {
            "README.md": true,
            "__tests__": {
              "Cursor.ts.skip": true
            },
            "index.d.ts": true,
            "index.js": true
          }
        },
        "dist": {
          "immutable-nonambient.d.ts": true,
          "immutable.d.ts": true,
          "immutable.es.js": true,
          "immutable.js": true,
          "immutable.js.flow": true,
          "immutable.min.js": true
        },
        "package.json": true
      }
    },
    "package.json": true,
    "yarn.lock": true
  }
}

I hope you enjoyed reading this post. For added explanation and other ways to use async generators, see this Q&A.

  • Related