2

I have a flat list (array of objects), like next one:

var myList = [
  {id:1, name:"ABC", type:"level_1"},
  {id:2, name:"XYZ", type:"level_1"},
  {id:1, name:"ABC_level 2", type:"level_2", level_one_id:1},
  {id:2, name:"XYZ_level 2", type:"level_2", level_one_id:2},
  {id:1, name:"ABC_level 3", type:"level_3", level_two_id:1},
  {id:2, name:"XYZ_level 3", type:"level_3", level_two_id:2},
];

Then, I have to group them in such a way that I can create a hierarchy of levels (which I tried to do in the below lines of code):

var myList = [
  {id:1, name:"ABC", type:"level_1"},
  {id:2, name:"XYZ", type:"level_1"},
  {id:1, name:"ABC_level 2", type:"level_2", level_one_id:1},
  {id:2, name:"XYZ_level 2", type:"level_2", level_one_id:2},
  {id:1, name:"ABC_level 3", type:"level_3", level_two_id:1},
  {id:2, name:"XYZ_level 3", type:"level_3", level_two_id:2},
];

var myNestedList = {
    levels: []
};
    
//-----------pushing level1----------

myList.forEach((res => {
    if (res.type == "level_1") {
        myNestedList.levels.push(res);
    }
}));

//-----------pushing level 2---------

myNestedList.levels.forEach((res) => {
    myList.forEach((val) => {
        if (val.type == "level_2" && val.level_one_id == res.id) {
            res["level_2"] = [] || res["level_2"];
            res["level_2"].push(val);
        }
    })
})
    
//-----------pushing level 3---------
    
myNestedList.levels.forEach((res) => {
    res["level_2"].forEach((val) => {
        myList.forEach((lastlevel) => {
            if (lastlevel.type == "level_3" && lastlevel.level_two_id == val.id) {
                val["level_3"] = [] || val["level_3"];
                val["level_3"].push(lastlevel);
            }
        })
    })
})

console.log(myNestedList);

Although I'm able to achieve the result, I'm sure this code can be more precise and meaningful. Can we make use of lodash here and get this code shorter?

Any help would be much appreciated. Thanks!

7
  • 1
    do you have a wanted result? why the changing name like level_one_id instead of a parent property? Commented Mar 5, 2020 at 19:02
  • @NinaScholz level_one_id: this will indicate me of the parent id and then I'll be able to group them accordingly. Commented Mar 5, 2020 at 19:09
  • 1
    I think the point is that if they had a consistent parent field, it would make life quite a bit easier to work with. But the repeated ids across levels would likely still cause problems. Commented Mar 5, 2020 at 19:14
  • @ScottSauyet This is the data I have it for now. But even in the case of consistent parent field, I have to loop them anyway with above code. Commented Mar 5, 2020 at 19:20
  • 1
    In other words, your tree structure is not at all obvious from the data supplied. What tells us that {id:2, name:"XYZ_level 2", type:"level_2", level_one_id:2} belongs under {id:2, name:"XYZ", type:"level_1"} and not under {id:1, name:"ABC", type:"level_1"}? Just the coincidence of the names XYZ vs ABC? Commented Mar 5, 2020 at 19:20

2 Answers 2

4

You could take a virtual unique id for the object and for referencing the parents and collect the items in a tree.

This approach works with unsorted data as well.

var data = [{ id: 1, name: "ABC", type: "level_1" }, { id: 2, name: "XYZ", type: "level_1" }, { id: 1, name: "ABC_level 2", type: "level_2", level_one_id: 1 }, { id: 2, name: "XYZ_level 2", type: "level_2", level_one_id: 2 }, { id: 1, name: "ABC_level 3", type: "level_3", level_two_id: 1 }, { id: 2, name: "XYZ_level 3", type: "level_3", level_two_id: 2 }],
    tree = function (data) {
        var t = {};
        data.forEach(o => {
            var level = o.type.match(/\d+$/)[0],
                parent = o[Object.keys(o).filter(k => k.startsWith('level_'))[0]] || 0,
                parentId = `${level - 1}.${parent}`,
                id = `${level}.${o.id}`,
                children = `level_${level}`;

            Object.assign(t[id] = t[id] || {}, o);
            t[parentId] = t[parentId] || {};
            t[parentId][children] = t[parentId][children] || [];
            t[parentId][children].push(t[id]);
        });
        return t['0.0'].level_1;
    }(data);

console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

4 Comments

Note that the original output (if that's really what's wanted) does not have children nodes. Instead it has nodes like level_2 and level_3.
the problem is, i see no wanted result. but it's no problem to address a dynamic children array.
I just ran the code supplied. I'm assuming that the output is what's desired, and the goal is cleaner code. But I could be way off.
I'm still pretty confused by the structure, myself!
2

I can't make any sense of this data representing a real tree. But I can see it turning into something like a list of lists, one for each base id, something like this:

[
  [
    {id: 1, name: "ABC", type: "level_1"},
    {id: 1, name: "ABC_level 2", type: "level_2", level_one_id: 1},
    {id: 1, name: "ABC_level 3", type: "level_3", level_two_id: 1}
  ],
  [
    {id: 2, name: "XYZ", type: "level_1"},
    {id: 2, name: "XYZ_level 2", type: "level_2", level_one_id: 2},
    {id: 2, name: "XYZ_level 3", type: "level_3", level_two_id: 2}
  ]
]

If that format is useful, then this code could help you get there:

// utility function
const group = (fn) => (xs) =>
  Object .values (xs .reduce ((a, x) => ({...a, [fn (x)]: (a [fn (x)] || []) .concat (x)}), {}))

// helper function
const numericSuffix = str => Number (str .type .match (/(\d+)$/) [1])

// main function -- I don't have a sense of what a good name for this would be
const foo = (xs) => 
  group (o => o.id) (xs)
    .map (x => x .sort ((a, b) => numericSuffix(a) - numericSuffix(b)))

// data
const myList = [{id: 1, name: "ABC", type: "level_1"}, {id: 2, name: "XYZ", type: "level_1"}, {id: 1, name: "ABC_level 2", type: "level_2", level_one_id: 1}, {id: 2, name: "XYZ_level 2", type: "level_2", level_one_id: 2}, {id: 1, name: "ABC_level 3", type: "level_3", level_two_id: 1}, {id: 2, name: "XYZ_level 3", type: "level_3", level_two_id: 2}]

// demo
console .log (foo (myList))
.as-console-wrapper {max-height: 100% !important; top: 0}

We use a custom group function as well as one that extracts the numeric end of a string (to be used in sorting so that level_10 comes after level_9 and not before level_2) group could be replaced by Underscore, lodash or Ramda groupBy functions, but you'd probably then have to call Object.values() on the results.

The main function groups the data on their ids, then sorts the group by that numeric suffix.

Note that this technique only makes sense if there is only one element for a given id at any particular level. If there could be more, and you really need a tree, I don't see how your input structure could determine future nesting.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.