1

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..

Abhinav
  • 1,202
  • 1
  • 8
  • 12
  • 1
    step 1- `var array = JSON.parse(your string goes here)` - then at least you have a JS array you can work with - the next bit is harder though – Jaromanda X Jan 14 '17 at 09:37
  • Yes, I'm stuck in next part... – Abhinav Jan 14 '17 at 13:15
  • You changed the required output in an essential way. Are you really sure you need it like that? What is the purpose to have items as array when you add essentially the same node there as the parent, and only add the children in the next level? This seems like a bad designed structure. – trincot Jan 14 '17 at 17:43
  • @trincot I'm trying to integrate navigation menu from http://multi-level-push-menu.make.rs/ into my project requirement for dynamic folder structure navigation, this plugin required JS array in this way. – Abhinav Jan 14 '17 at 18:04

3 Answers3

1

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; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Thanks a ton, this was helpful, but there can be a multiple folder into a root folder under config – Abhinav Jan 14 '17 at 13:16
  • Thanks for edit it works fine, however I discover some more edits while integrating your solutions, if you could help... sorry for not having closer look over required input earlier wile posting question.. – Abhinav Jan 14 '17 at 17:32
0

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:

Community
  • 1
  • 1
Daniel
  • 611
  • 5
  • 20
0

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

Update after updated question

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

ES5 version

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; }
Community
  • 1
  • 1
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thanks for your code, it helps.. however I discover new edits required to be made, if you could help? – Abhinav Jan 14 '17 at 17:28
  • Sure, but your last update to the question breaks the logic. How do the input and the output relate now? It is not really fair to change the question in such essential way, when people have spent time on solving the original question. Also, it is not really an `id` any more when it can have duplicates. – trincot Jan 14 '17 at 17:34
  • apology for change and not having closer look at requirement. Id is same as of name and its not gonna be same, it will be into title element only repeating. I'm trying to integrate navigation menu from http://multi-level-push-menu.make.rs/ into my code for folder structure navigation. – Abhinav Jan 14 '17 at 17:57
  • I added a new section to my answer, which produces the new structure. – trincot Jan 14 '17 at 19:11
  • Thanks Ton Sir.. this works perfect.. Appreciated you help :) :) – Abhinav Jan 14 '17 at 19:40
  • one more challenge, its working perfect for Chrome, Firefox but not IE. Its showing Expected Identified in console. – Abhinav Jan 17 '17 at 06:58
  • IE does not fully support ES6, and probably never will, since Edge is the browser that Microsoft is pushing instead. Do you need this to work in IE? – trincot Jan 17 '17 at 08:37
  • Anyway I added it. – trincot Jan 17 '17 at 09:04
  • Thanks .. it worked well.. :) we are targeting our app for min IE11 that the reason it was needed to work on IE as well.. – Abhinav Jan 17 '17 at 09:17