9

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": [] } ] } ] } ]; 

2 Answers 2

15

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

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

6 Comments

i wonder which is faster, this, or @Jonasw 's answer. any thought? This for me is much more readable though. woudl up twice if posible. :D
@GeomanYabes this equals my first answer ( however it looks much better)
@GeomanYabes This recursive function closely follows the resulting tree structure and searches for matching children within an array in linear time. This could be improved by directly mapping children names to object properties of the parent tree node and converting to OP's array layout later on. Which would come with an additional constant cost, so probably undesired unless OP deals with a lot of paths.
ohh.. i see.. was just taking your "improved" answer as your answer.
Thanks @le_m! Great solution. I will probably go for a es5 approach due to project requirements, but I will mark this as answer for future reference.
|
1

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

7 Comments

dir.children[sub] will not return anything. It will be looking for dir.children["Folder 1"], etc. dir.children is an array of objects, not an object itself. You will be adding properties to the array object, which is an anti-pattern if I've ever seen one. Your output will not look like the OP's desired output
Looks like it works now. If it had some whitespace I'd upvote it.
There ya go, now it looks right. Not a fan of assignments inside conditional expressions, but it works.
This will fail if there is a single string with different root, like "/path/library/Folder 1",
@AvraamMavridis Yup, it is does not handle multiple root structures, nor does it handle dynamic depth
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.