Search code examples
javascriptecmascript-5

Nested grouping with javascript (ES5)


I have an array of objects as following :

    [
        {"id":1,"lib":"A","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
        {"id":2,"lib":"B","categoryID":10,"categoryTitle":"Cat10","moduleID":"2","moduleTitle":"Module 2"},
        ...
        {"id":110,"lib":"XXX","categoryID":90,"categoryTitle":"Cat90","moduleID":"4","moduleTitle":"Module 4"}
    ]

I want to group this array by (moduleID,moduleTitle) and then by (categoryID,categoryTitle).

This is what I tried :

function groupBy(data, id, text) {

    return data.reduce(function (rv, x) { 
        var el = rv.find(function(r){
            return r && r.id === x[id];
        });
        if (el) { 
            el.children.push(x);
        } else { 
            rv.push({ id: x[id], text: x[text], children: [x] }); 
        } 
        return rv; 
    }, []);

}

var result = groupBy(response, "moduleID", "moduleTitle");

result.forEach(function(el){
    el.children = groupBy(el.children, "categoryID", "categoryTitle");
});

The above code is working as expected, but as you can see, after the first grouping I had to iterate again over the array which was grouped by the moduleId in order to group by the categoryId.

How can I modify this code so I can only call groupBy function once on the array ?

Edit:

Sorry this might be late, but I want this done by using ES5, no Shim and no Polyfill too.


Solution

  • You could extend your function by using an array for the grouping id/names.

    function groupBy(data, groups) {
        return data.reduce(function (rv, x) {
            groups.reduce(function (level, key) {
                var el;
    
                level.some(function (r) {
                    if (r && r.id === x[key[0]]) {
                        el = r;
                        return true;
                    }
                });
                if (!el) {
                    el = { id: x[key[0]], text: x[key[1]], children: [] };
                    level.push(el);
                }
                return el.children;
            }, rv).push({ id: x.id, text: x.lib });
            return rv;
        }, []);
    }
    
    var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }],
        result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
    
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    Version with path as id.

    function groupBy(data, groups) {
        return data.reduce(function (rv, x) {
            var path = [];
            var last = groups.reduce(function (level, key, i) {
                path.length = i;
                path[i] = key[0].slice(0, -2).toUpperCase() + ':' + x[key[0]];
    
                var id = path.join(';'),
                    el = level.find(function (r) {
                        return r && r.id === id;
                    });
    
                if (!el) {
                    el = { id: path.join(';'), text: x[key[1]], children: [] };
                    level.push(el);
                }
                return el.children;
            }, rv);
    
            last.push({ id: path.concat('NODE:' + x.id).join(';') });
            return rv;
        }, []);
    }
    
    var response = [{ id: 1, lib: "A", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Workflow" }, { id: 2, lib: "B", categoryID: 10, categoryTitle: "Cat10", moduleID: "2", moduleTitle: "Module 2" }, { id: 110, lib: "XXX", categoryID: 90, categoryTitle: "Cat90", moduleID: "4", moduleTitle: "Module 4" }];
    
        var result = groupBy(response, [["moduleID", "moduleTitle"], ["categoryID", "categoryTitle"]]);
    console.log(result);
    .as-console-wrapper { max-height: 100% !important; top: 0; }