3
votes

I have a flat array and I want to build a tree array in node.js. The array I have is like:

var data=  [
        {
            "id1": 1001,
            "id2": 1002,
            "id3": 1004,
            "id4": 1007,
            "id5": 1013,
            "id6": 1030,
            "id7": null,
            "id8": null,
            "id9": null,
            "accountid": 1108,
            "name1": "Net Income",
            "name2": "Net Income Before Extraordinary Item",
            "name3": "Income (Loss) from operations                     ",
            "name4": "Net Patient Revenue",
            "name5": "Gross Patient Revenue",
            "name6": "Inpatient Routine Services",
            "name7": null,
            "name8": null,
            "name9": null,
            "accountname": "3000000 - INPATIENT REVENUE                       ",
            "amt1": 6266235,
            "amt2": 0,
            "amt3": 7085312,
            "amt4": 7010901,
            "amt5": 7008743,
            "amt6": 6865373,
            "amt7": 7298176,
            "amt8": 7481711,
            "nCurMnth": 0,
            "samt4": 7010901,
            "id": 1030,
            "parentid": 1013,
            "relativelevel": 6,
            "accountdetailid": null,
            "variancecalc": 1,
            "InfoSetID": 1108,
            "InfoSetName": "3000000 - INPATIENT REVENUE                       ",
            "DetailType": "",
            "DecimalCount": 2
        },
        {
            "id1": 1001,
            "id2": 1002,
            "id3": 1004,
            "id4": 1007,
            "id5": 1013,
            "id6": 1030,
            "id7": null,
            "id8": null,
            "id9": null,
            "accountid": 2708,
            "name1": "Net Income",
            "name2": "Net Income Before Extraordinary Item",
            "name3": "Income (Loss) from operations                     ",
            "name4": "Net Patient Revenue",
            "name5": "Gross Patient Revenue",
            "name6": "Inpatient Routine Services",
            "name7": null,
            "name8": null,
            "name9": null,
            "accountname": "3000090 - INPATIENT SWING BED                     ",
            "amt1": 190887,
            "amt2": 0,
            "amt3": 256581,
            "amt4": 271789,
            "amt5": 235998,
            "amt6": 251224,
            "amt7": 307154,
            "amt8": 314971,
            "nCurMnth": 0,
            "samt4": 271789,
            "id": 1030,
            "parentid": 1013,
            "relativelevel": 6,
            "accountdetailid": null,
            "variancecalc": 1,
            "InfoSetID": 2708,
            "InfoSetName": "3000090 - INPATIENT SWING BED                     ",
            "DetailType": "",
            "DecimalCount": 2
        },
        {
            "id1": 1001,
            "id2": 1002,
            "id3": 1004,
            "id4": 1007,
            "id5": 1013,
            "id6": 1030,
            "id7": null,
            "id8": null,
            "id9": null,
            "accountid": 1114,
            "name1": "Net Income",
            "name2": "Net Income Before Extraordinary Item",
            "name3": "Income (Loss) from operations                     ",
            "name4": "Net Patient Revenue",
            "name5": "Gross Patient Revenue",
            "name6": "Inpatient Routine Services",
            "name7": null,
            "name8": null,
            "name9": null,
            "accountname": "3999000 - SUSPENSE DAILY REV CLEAR ACCT           ",
            "amt1": 0,
            "amt2": 0,
            "amt3": 0,
            "amt4": 0,
            "amt5": 0,
            "amt6": 0,
            "amt7": 0,
            "amt8": 0,
            "nCurMnth": 0,
            "samt4": 0,
            "id": 1030,
            "parentid": 1013,
            "relativelevel": 6,
            "accountdetailid": null,
            "variancecalc": 1,
            "InfoSetID": 1114,
            "InfoSetName": "3999000 - SUSPENSE DAILY REV CLEAR ACCT           ",
            "DetailType": "",
            "DecimalCount": 2
        },
        {
            "id1": 1001,
            "id2": 1002,
            "id3": 1004,
            "id4": 1007,
            "id5": 1013,
            "id6": 1031,
            "id7": null,
            "id8": null,
            "id9": null,
            "accountid": 1133,
            "name1": "Net Income",
            "name2": "Net Income Before Extraordinary Item",
            "name3": "Income (Loss) from operations                     ",
            "name4": "Net Patient Revenue",
            "name5": "Gross Patient Revenue",
            "name6": "Inpatient Ancillary Services",
            "name7": null,
            "name8": null,
            "name9": null,
            "accountname": "3070000 - INTERP - INP                            ",
            "amt1": 36886,
            "amt2": 0,
            "amt3": 47968,
            "amt4": 45109,
            "amt5": 38047,
            "amt6": 39158,
            "amt7": 31290,
            "amt8": 45148,
            "nCurMnth": 0,
            "samt4": 45109,
            "id": 1031,
            "parentid": 1013,
            "relativelevel": 6,
            "accountdetailid": null,
            "variancecalc": 1,
            "InfoSetID": 1133,
            "InfoSetName": "3070000 - INTERP - INP                            ",
            "DetailType": "",
            "DecimalCount": 2
        },
        {
            "id1": 1001,
            "id2": 1002,
            "id3": 1004,
            "id4": 1007,
            "id5": 1013,
            "id6": 1031,
            "id7": null,
            "id8": null,
            "id9": null,
            "accountid": 1135,
            "name1": "Net Income",
            "name2": "Net Income Before Extraordinary Item",
            "name3": "Income (Loss) from operations                     ",
            "name4": "Net Patient Revenue",
            "name5": "Gross Patient Revenue",
            "name6": "Inpatient Ancillary Services",
            "name7": null,
            "name8": null,
            "name9": null,
            "accountname": "3100000 - INPATIENT ANCILLARY REV                 ",
            "amt1": 18822593,
            "amt2": 0,
            "amt3": 21676463,
            "amt4": 21368866,
            "amt5": 20284449,
            "amt6": 21344632,
            "amt7": 20272660,
            "amt8": 21169123,
            "nCurMnth": 0,
            "samt4": 21368866,
            "id": 1031,
            "parentid": 1013,
            "relativelevel": 6,
            "accountdetailid": null,
            "variancecalc": 1,
            "InfoSetID": 1135,
            "InfoSetName": "3100000 - INPATIENT ANCILLARY REV                 ",
            "DetailType": "",
            "DecimalCount": 2
        },
        {
            "id1": 1001,
            "id2": 1002,
            "id3": 1004,
            "id4": 1007,
            "id5": 1013,
            "id6": 1031,
            "id7": null,
            "id8": null,
            "id9": null,
            "accountid": 1273,
            "name1": "Net Income",
            "name2": "Net Income Before Extraordinary Item",
            "name3": "Income (Loss) from operations                     ",
            "name4": "Net Patient Revenue",
            "name5": "Gross Patient Revenue",
            "name6": "Inpatient Ancillary Services",
            "name7": null,
            "name8": null,
            "name9": null,
            "accountname": "3100090 - SWING BED INPATIENT REV                 ",
            "amt1": 166993,
            "amt2": 0,
            "amt3": 225651,
            "amt4": 228349,
            "amt5": 143726,
            "amt6": 227736,
            "amt7": 235705,
            "amt8": 206381,
            "nCurMnth": 0,
            "samt4": 228349,
            "id": 1031,
            "parentid": 1013,
            "relativelevel": 6,
            "accountdetailid": null,
            "variancecalc": 1,
            "InfoSetID": 1273,
            "InfoSetName": "3100090 - SWING BED INPATIENT REV                 ",
            "DetailType": "",
            "DecimalCount": 2
        }
    ]

NOTE:- id1 is the root and its name is represented by name1, id2 is the immediate child of id1 and its name is name2, similarly id3 is the immediate child of id2 and its name is name3 and so on.

Now I want a hierarchical (tree like) structure represented by names in node.js.

I would also like to know if there is any package which can do this.

Thanks in advance.

Expected output in tree form is like:

Net Income
   |
   ---------Net Income Before Extraordinary Item
            |
            ------Income (Loss) from operations
            |     |
            |     ------Net Patient Revenue   
            |           |
            |            --------- 
            ------XYZ
2
Do you need something like [{ "Net income": 36886, "Net income before etc": 0, ... }, { "Net income": 18822593, ... }, ...]. If not, could you edit your question and add an example of the results you want? - David González
Sorry I didn't see your edit. I posted an answer but I'm not sure if it's what you want. Do you need to add more data to the resulting object? - David González

2 Answers

0
votes

Ok i have a solid answer for this nice question. But before i go into the solution details here is the story part.

Actually this question addresses a very common problem in modern JS programing which is getting and setting nested values dynamically. I had developed two Object methods to cover this need in a functional manner. They are called Object.prototype.getNestedValue() and Object.prototype.setNestedValue() which both work on getting and setting objects and array properties and values dynamically.

getNestedValue([prop1[, prop2[, prop3...]]]) will return you the value of a nested property. You can invoke it like

`myObj.getNestedValue(a,"prop2",1);`

in which myObj is the object it's invoked upon, a is a dynamic variable (if string type represents an object property but if it's a number type then it is an array index value), "prop2" is a static property argument and the last one 1 is the index of an array. All provided arguments can of course be dynamic. If you have your arguments in an array you can invoke it like getNestedValue(...[a,b,c,d]);

setNestedValue([prop1[, prop2[, prop3...]]],value) works exactly like it's twin but the last argument is the value to set. If the property does not exist it will create an object or array depenging on the type of the provided argument. Again, a string type argument will result an object while a number type argument will result an Array of that size.

So as you will see once we have these tools in our hand the problem subject to this question is no more a problem. Lets see the code.

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
Object.prototype.setNestedValue = function(...a) {
  a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
                                                                       : (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
                                                                         this[a[0]].setNestedValue(...a.slice(1)))
               : this[a[0]] = a[1];
  return this;
};

var   dataStr = '[{"id1":1001,"id2":1002,"id3":1004,"id4":1007,"id5":1013,"id6":1030,"id7":null,"id8":null,"id9":null,"accountid":1108,"name1":"Net Income","name2":"Net Income Before Extraordinary Item","name3":"Income (Loss) from operations                     ","name4":"Net Patient Revenue","name5":"Gross Patient Revenue","name6":"Inpatient Routine Services","name7":null,"name8":null,"name9":null,"accountname":"3000000 - INPATIENT REVENUE                       ","amt1":6266235,"amt2":0,"amt3":7085312,"amt4":7010901,"amt5":7008743,"amt6":6865373,"amt7":7298176,"amt8":7481711,"nCurMnth":0,"samt4":7010901,"id":1030,"parentid":1013,"relativelevel":6,"accountdetailid":null,"variancecalc":1,"InfoSetID":1108,"InfoSetName":"3000000 - INPATIENT REVENUE                       ","DetailType":"","DecimalCount":2},{"id1":1001,"id2":1002,"id3":1004,"id4":1007,"id5":1013,"id6":1030,"id7":null,"id8":null,"id9":null,"accountid":2708,"name1":"Net Income","name2":"Net Income Before Extraordinary Item","name3":"Income (Loss) from operations                     ","name4":"Net Patient Revenue","name5":"Gross Patient Revenue","name6":"Inpatient Routine Services","name7":null,"name8":null,"name9":null,"accountname":"3000090 - INPATIENT SWING BED                     ","amt1":190887,"amt2":0,"amt3":256581,"amt4":271789,"amt5":235998,"amt6":251224,"amt7":307154,"amt8":314971,"nCurMnth":0,"samt4":271789,"id":1030,"parentid":1013,"relativelevel":6,"accountdetailid":null,"variancecalc":1,"InfoSetID":2708,"InfoSetName":"3000090 - INPATIENT SWING BED                     ","DetailType":"","DecimalCount":2},{"id1":1001,"id2":1002,"id3":1004,"id4":1007,"id5":1013,"id6":1030,"id7":null,"id8":null,"id9":null,"accountid":1114,"name1":"Net Income","name2":"Net Income Before Extraordinary Item","name3":"Income (Loss) from operations                     ","name4":"Net Patient Revenue","name5":"Gross Patient Revenue","name6":"Inpatient Routine Services","name7":null,"name8":null,"name9":null,"accountname":"3999000 - SUSPENSE DAILY REV CLEAR ACCT           ","amt1":0,"amt2":0,"amt3":0,"amt4":0,"amt5":0,"amt6":0,"amt7":0,"amt8":0,"nCurMnth":0,"samt4":0,"id":1030,"parentid":1013,"relativelevel":6,"accountdetailid":null,"variancecalc":1,"InfoSetID":1114,"InfoSetName":"3999000 - SUSPENSE DAILY REV CLEAR ACCT           ","DetailType":"","DecimalCount":2},{"id1":1001,"id2":1002,"id3":1004,"id4":1007,"id5":1013,"id6":1031,"id7":null,"id8":null,"id9":null,"accountid":1133,"name1":"Net Income","name2":"Net Income Before Extraordinary Item","name3":"Income (Loss) from operations                     ","name4":"Net Patient Revenue","name5":"Gross Patient Revenue","name6":"Inpatient Ancillary Services","name7":null,"name8":null,"name9":null,"accountname":"3070000 - INTERP - INP                            ","amt1":36886,"amt2":0,"amt3":47968,"amt4":45109,"amt5":38047,"amt6":39158,"amt7":31290,"amt8":45148,"nCurMnth":0,"samt4":45109,"id":1031,"parentid":1013,"relativelevel":6,"accountdetailid":null,"variancecalc":1,"InfoSetID":1133,"InfoSetName":"3070000 - INTERP - INP                            ","DetailType":"","DecimalCount":2},{"id1":1001,"id2":1002,"id3":1004,"id4":1007,"id5":1013,"id6":1031,"id7":null,"id8":null,"id9":null,"accountid":1135,"name1":"Net Income","name2":"Net Income Before Extraordinary Item","name3":"Income (Loss) from operations                     ","name4":"Net Patient Revenue","name5":"Gross Patient Revenue","name6":"Inpatient Ancillary Services","name7":null,"name8":null,"name9":null,"accountname":"3100000 - INPATIENT ANCILLARY REV                 ","amt1":18822593,"amt2":0,"amt3":21676463,"amt4":21368866,"amt5":20284449,"amt6":21344632,"amt7":20272660,"amt8":21169123,"nCurMnth":0,"samt4":21368866,"id":1031,"parentid":1013,"relativelevel":6,"accountdetailid":null,"variancecalc":1,"InfoSetID":1135,"InfoSetName":"3100000 - INPATIENT ANCILLARY REV                 ","DetailType":"","DecimalCount":2},{"id1":1001,"id2":1002,"id3":1004,"id4":1007,"id5":1013,"id6":1031,"id7":null,"id8":null,"id9":null,"accountid":1273,"name1":"Net Income","name2":"Net Income Before Extraordinary Item","name3":"Income (Loss) from operations                     ","name4":"Net Patient Revenue","name5":"Gross Patient Revenue","name6":"Inpatient Ancillary Services","name7":null,"name8":null,"name9":null,"accountname":"3100090 - SWING BED INPATIENT REV                 ","amt1":166993,"amt2":0,"amt3":225651,"amt4":228349,"amt5":143726,"amt6":227736,"amt7":235705,"amt8":206381,"nCurMnth":0,"samt4":228349,"id":1031,"parentid":1013,"relativelevel":6,"accountdetailid":null,"variancecalc":1,"InfoSetID":1273,"InfoSetName":"3100090 - SWING BED INPATIENT REV                 ","DetailType":"","DecimalCount":2}]',
         data = JSON.parse(dataStr);

getKeysToNest = (o,...k) => Object.keys(o).reduce((p,c) => {var i = k.indexOf(c.slice(0, -1));
                                                            !!~i && (p[i] = !!p[i] ? p[i].concat(c) : [c]);
                                                            return p},[]),
        props = getKeysToNest(data[0],"id","name","amt"), // now we have the props to nest
       nested = data.map(d => props
                    .reduce((n,p) => p
                    .reduce((o,k,i) => o.setNestedValue(...(new Array(i)).fill("child"),k,d[k]),n) ,{}));
console.log(JSON.stringify(nested,null,2));

We actually use the getKeysToNest() function here to collect the keys that are important for us and we store them in an array. Then we will use this props array to dynamically set the arguments of setNestedValue() method. It's a very simple workflow;

  • We map each object in the item to it's nested form. It's the data.map(d => props line.
  • props array contains 3 arrays one for each id,name and amt. We take them one by one in .reduce((n,p) => p line.
  • An in the last line we are setting the properties dynamically per key (runs once for id, name and amt and once per each level of each key)

I just couldn't figure out where to put the remaining properties. It's just a matter of filtering them out and insering tham at the appropriate level by utilizing setNestedValue() again.

If any questions i will be happy to answer.

0
votes

I don't know if there's a package for this, on the other hand it seems it can be easily solved by travelling the names (directly as they match the id's). This will indent the entries as much as relativelevel says, putting them in the attribute entry of each object.

// Configuration
var max_attrs = 9; // Max attrs (ex: name1 .. name9).
var ename = 'entry'; // Name of the entry.

var output = {};

function process_item(item) {
    // Dig into output
    var dig_n = item.relativelevel;
    var dig = output;
    while (--dig_n) {
        if (!(ename in dig))
            dig[ename] = {};
        dig = dig[ename];
    }

    // Generate entry
    var i = max_attrs;
    var entry;

    while (--i) {
        var name = item["name" + i];
        if (name) {
            if (!entry)
                entry = name;
            else {
                var entry_add = {};
                entry_add[name] = entry;
                entry = entry_add;
            }
        }
    };

    dig[ename] = entry  
}

data.forEach(function(item) {
    process_item(item);
})

// tree will be in variable output
console.log(JSON.stringify(output))