Abhinav
Abhinav

Reputation: 1200

JSON String to JS Array

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

Answers (3)

trincot
trincot

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

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

Upvotes: 0

Nina Scholz
Nina Scholz

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

Daniel
Daniel

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

Related Questions