Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create nested object from multiple string paths

Tags:

javascript

I'm looking for the best way to convert multiple string paths to a nested object with javascript. I'm using lodash if that could help in any way.

I got the following paths:

/root/library/Folder 1
/root/library/Folder 2
/root/library/Folder 1/Document.docx
/root/library/Folder 1/Document 2.docx
/root/library/Folder 2/Document 3.docx
/root/library/Document 4.docx

and I would like to create the following array of object:

  var objectArray =
    [
      {
        "name": "root", "children": [
          {
            "name": "library", "children": [
              {
                "name": "Folder 1", "children": [
                  { "name": "Document.docx", "children": [] },
                  { "name": "Document 2.docx", "children": [] }
                ]
              },
              {
                "name": "Folder 2", "children": [
                  { "name": "Document 3.docx", "children": [] }
                ]
              },
              {
                "name": "Document 4.docx", "children": []
              }
            ]
          }
        ]
      }
    ];
like image 287
ffffff01 Avatar asked Jun 21 '17 15:06

ffffff01


People also ask

How to access nested JavaScript objects with string key?

Accessing nested JavaScript objects with string key. You can use lodash's get method to get properties at any level safely. Getting first-level properties is pretty straightforward. Nested property access is tricky and you should use a tested library like lodash for it.

How do I access a nested object in a document?

Each nested object must have a unique access path. The same field name can occur in nested objects in the same document. However, the full access name must still be unique. To access nested fields, concatenate the field names with a . (dot) as separator. For example, use author.lastname to access the surname for the author in this document:

How to get first-level properties of a nested object?

Getting first-level properties is pretty straightforward. Nested property access is tricky and you should use a tested library like lodash for it. You can access a deeply nested object in the following way −

What is the problem with the first approach to nested objects?

The problem with the first approach is that the output gives no idea about the nested object or the class A and its attributes, and if that is ones requirement then we are good to go.


2 Answers

I suggest implementing a tree insertion function whose arguments are an array of children and a path. It traverses the children according to the given path and inserts new children as necessary, avoiding duplicates:

// Insert path into directory tree structure:
function insert(children = [], [head, ...tail]) {
  let child = children.find(child => child.name === head);
  if (!child) children.push(child = {name: head, children: []});
  if (tail.length > 0) insert(child.children, tail);
  return children;
}

// Example:
let paths = [
  '/root/library/Folder 1',
  '/root/library/Folder 2',
  '/root/library/Folder 1/Document.docx',
  '/root/library/Folder 1/Document 2.docx',
  '/root/library/Folder 2/Document 3.docx',
  '/root/library/Document 4.docx'
];

let objectArray = paths
  .map(path => path.split('/').slice(1))
  .reduce((children, path) => insert(children, path), []);

console.log(objectArray);
like image 68
le_m Avatar answered Oct 26 '22 22:10

le_m


Iterate over each string and resolve it to an object:

var glob={name:undefined,children:[]};

["/root/library/Folder 1","/root/library/Folder 2","/root/library/Folder 1/Document.docx","/root/library/Folder 1/Document 2.docx","/root/library/Folder 2/Document 3.docx","/root/library/Document 4.docx"]
.forEach(function(path){

  path.split("/").slice(1).reduce(function(dir,sub){

     var children;

     if(children=dir.children.find(el=>el.name===sub)){
       return children;
     }

     children={name:sub,children:[]};
     dir.children.push(children);
     return children;

  },glob);

});

console.log(glob);

http://jsbin.com/yusopiguci/edit?console


Improved version:

var glob={name:undefined,children:[]};
var symbol="/" /* or Symbol("lookup") in modern browsers */ ;
var lookup={[symbol]:glob};

["/root/library/Folder 1","/root/library/Folder 2","/root/library/Folder 1/Document.docx","/root/library/Folder 1/Document 2.docx","/root/library/Folder 2/Document 3.docx","/root/library/Document 4.docx"]
.forEach(function(path){

  path.split("/").slice(1).reduce(function(dir,sub){
     if(!dir[sub]){
      let subObj={name:sub,children:[]};
      dir[symbol].children.push(subObj);
      return dir[sub]={[symbol]:subObj};
    }
    return dir[sub];
  },lookup);

});

console.log(glob);

It creates the same result but it is may much faster ( up to O(n) vs. O(n+n!)) http://jsbin.com/xumazinesa/edit?console

like image 35
Jonas Wilms Avatar answered Oct 26 '22 22:10

Jonas Wilms