Reputation: 1200
How to convert folder structure JSON String to JS Array. I've following JSON string
[{ "Name": "A", "ParentName": "Config", "Type": "default" },
{ "Name": "SubA", "ParentName": "A", "Type": "default" },
{ "Name": "SubAFile", "ParentName": "SubA", "Type": "file" },
{ "Name": "B", "ParentName": "Config", "Type": "default" },
{ "Name": "C", "ParentName": "Config", "Type": "default" }]
I want to make JS Array object out of this in following format
var NewStr = [{
"name": 'A',
"id": 'A',
"icon": 'fa fa-folder',
"items": [{
"title": "A",
"icon": "fa fa-folder",
"id": "A",
"items": [{
"name": "subA",
"icon": "fa fa-folder",
"id": "subA",
"items": [{
"title": "SubA",
"icon": "fa fa-folder",
"id": "SubA",
"items": [{
"name": "SubAFile",
"icon": "fa fa-file"
}]
}]
}]
}]
}, {
"name": 'B',
"id": 'b',
"icon": "fa fa-folder"
}, {
"name": 'C',
"id": 'C',
"icon": "fa fa-folder"
}];
Note: ParentName I've included to identify hierarchy of folder structure. ID will be same as of name.
Any suggestion for this?
Thanks..
Upvotes: 1
Views: 463
Reputation: 350365
You could use a Map
to key the nodes by name, and build the tree while iterating over the input with reduce
. For each node create a parent node if it does not yet exist. When this happens, remember this newly created parent as the root of the tree: its children are the array you want to produce.
Here is the ES6 code:
// Sample input JSON parsed:
const items = JSON.parse('[{"Name":"A","ParentName":"Config","Type":"default"},{"Name":"new","ParentName":"A","Type":"file"},{"Name":"B","ParentName":"Config","Type":"default"},{"Name":"C","ParentName":"Config","Type":"default"}]');
const arr = items.reduce( ([nodes, root], {Name, ParentName, Type}) => {
const node = Object.assign({ // create node
name: Name,
icon: Type == 'default' ? 'fa fa-folder' : 'fa fa-file'
}, nodes.get(Name)); // add previously registered children, if any
const parent = nodes.get(ParentName) || (root = {}); // create parent if not present
parent.items = (parent.items || []).concat(node); // add current as child
return [nodes.set(Name, node).set(ParentName, parent), root];
}, [new Map, {}] )[1].items; // start with empty map, return the items of the root
// Output result
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
In the update of your question the desired output was changed, having more nested levels: nodes with children now need an intermediate object as child (with mostly the same properties) which in turn has the children nodes attached to its own items
property.
Here is the ES6 code adapted for that purpose:
function buildTree(folders) {
const [nodes, root] = folders.reduce( ([nodes, root], {Name, ParentName, Type}) => {
const node = Object.assign({ // create node
name: Name,
id: Name,
icon: Type == 'default' ? 'fa fa-folder' : 'fa fa-file'
}, nodes.get(Name)); // add previously registered children, if any
const parent = nodes.get(ParentName) || (root = {}); // create parent if not present
parent.items = (parent.items || []).concat(node); // add current as child
return [nodes.set(Name, node).set(ParentName, parent), root];
}, [new Map, {}] );
// To add the extra intermediate levels (requested in updated question):
nodes.forEach( node => {
if (node.items) node.items = [{
title: node.name,
icon: node.icon,
id: node.id,
items: node.items
}]
});
return root.items[0].items;
}
// Sample JSON data, parsed
const folders = JSON.parse('[{ "Name": "A", "ParentName": "Config", "Type": "default" },{ "Name": "SubA", "ParentName": "A", "Type": "default" },{ "Name": "SubAFile", "ParentName": "SubA", "Type": "file" },{ "Name": "B", "ParentName": "Config", "Type": "default" },{ "Name": "C", "ParentName": "Config", "Type": "default" }]');
const arr = buildTree(folders);
// Output result
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For the browsers that have little ES6 support (like IE):
function buildTree(folders) {
var result = folders.reduce(function (acc, obj) {
var nodes = acc[0];
var root = acc[1];
var node = { // create node
name: obj.Name,
id: obj.Name,
icon: obj.Type == 'default' ? 'fa fa-folder' : 'fa fa-file'
};
// add previously registered children, if any
if (nodes[obj.Name]) node.items = nodes[obj.Name].items;
var parent = nodes[obj.ParentName] || (root = {}); // create parent if not present
parent.items = (parent.items || []).concat(node); // add current as child
nodes[obj.Name] = node;
nodes[obj.ParentName] = parent;
return [nodes, root];
}, [{}, {}] );
// To add the extra intermediate levels (requested in updated question):
for (var name in result[0]) {
var node = result[0][name];
if (node.items) node.items = [{
title: node.name,
icon: node.icon,
id: node.id,
items: node.items
}];
}
return result[1].items[0].items;
}
// Sample JSON data, parsed
var folders = JSON.parse('[{ "Name": "A", "ParentName": "Config", "Type": "default" },{ "Name": "SubA", "ParentName": "A", "Type": "default" },{ "Name": "SubAFile", "ParentName": "SubA", "Type": "file" },{ "Name": "B", "ParentName": "Config", "Type": "default" },{ "Name": "C", "ParentName": "Config", "Type": "default" }]');
var arr = buildTree(folders);
// Output result
console.log(arr);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 0
Reputation: 386654
First use JSON.parse
for generating an obbject from a valid JSON string.
The
JSON.parse()
method parses a JSON string, constructing the JavaScript value or object described by the string. An optional reviver function can be provided to perform a transformation on the resulting object before it is returned.
Then you could use an iterative approach for generating a tree with creating a new object with the wanted properties for referencing inserted or referenced parent objects, a temporary object is used.
This works for unsorted and nested items as well.
var data = [{ Name: "A", ParentName: "Config", Type: "default" }, { Name: "SubA", ParentName: "A", Type: "default" }, { Name: "SubAFile", ParentName: "SubA", Type: "file" }, { Name: "B", ParentName: "Config", Type: "default" }, { Name: "C", ParentName: "Config", Type: "default" }],
tree = function (data, root) {
var r = [], o = {};
data.forEach(function (a) {
var temp = { name: a.Name, icon: a.Type === 'file' ? 'fa fa-file' : 'fa fa-folder' };
if (o[a.Name] && o[a.Name].items) {
temp.items = o[a.Name].items;
}
o[a.Name] = temp;
if (a.ParentName === root) {
r.push(temp);
} else {
o[a.ParentName] = o[a.ParentName] || {};
o[a.ParentName].items = o[a.ParentName].items || [];
o[a.ParentName].items.push(temp);
}
});
return r;
}(data, 'Config');
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 1
Reputation: 631
Such a great quiz! I'll promise you to find solution in a couple of hours. What you gave is a kind of "reverse binary tree" and the current solution in terms of BigO looks too ugly by my own.
If you don't mind I will post draft preSolution and keep thinking about more right way to increase productivity and edit it a bit later.
var tree = {}
function findParent(data, parentName){
var parentExist = false;
$.each(data, function(index, value){
parentExist = (value.name == parentName);
if(parentExist){
moveChild(data, index, tree, tree[value.parentName]);
} else {
createParent(parentName, tree);
}
}
}
function moveChild(collectionIn, child, collectionOut, parent){
collectionOut[parent].push(collectionIn[child]);
splice(collectionIn[child], 1);
}
function createParent(parentName, targetTree);
$.each(data, function(index, val){
findParent(index.parentName);
});
tips I'll got to check:
Upvotes: 0