building a menu list object recursively in javascript











up vote
4
down vote

favorite
1












with an array of



['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];


I'd like to construct an map object that looks like



{
'social': {
swipes: {
women: null,
men: null
}
},
'upgrade': {
premium: null
}





const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};

const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}

const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";

for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}

buildMenuMap(menu);

console.log(map);





but I'm unsure how to switch the value of root



what do I set root to so that it recursively calls addLabelToMap with



'[social]', 'swipes' => '[social][swipes]', 'women' => '[social][swipes]', 'men'?



I've used root = root[element] but it's giving an error



alternative solutions would be great, but I'd like to understand why this isn't working fundamentally










share|improve this question
























  • Shouldn't men be in the likes object not the swipes object?
    – ibrahim mahrir
    Nov 14 at 21:22










  • I've just edited it
    – totalnoob
    Nov 14 at 21:23















up vote
4
down vote

favorite
1












with an array of



['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];


I'd like to construct an map object that looks like



{
'social': {
swipes: {
women: null,
men: null
}
},
'upgrade': {
premium: null
}





const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};

const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}

const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";

for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}

buildMenuMap(menu);

console.log(map);





but I'm unsure how to switch the value of root



what do I set root to so that it recursively calls addLabelToMap with



'[social]', 'swipes' => '[social][swipes]', 'women' => '[social][swipes]', 'men'?



I've used root = root[element] but it's giving an error



alternative solutions would be great, but I'd like to understand why this isn't working fundamentally










share|improve this question
























  • Shouldn't men be in the likes object not the swipes object?
    – ibrahim mahrir
    Nov 14 at 21:22










  • I've just edited it
    – totalnoob
    Nov 14 at 21:23













up vote
4
down vote

favorite
1









up vote
4
down vote

favorite
1






1





with an array of



['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];


I'd like to construct an map object that looks like



{
'social': {
swipes: {
women: null,
men: null
}
},
'upgrade': {
premium: null
}





const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};

const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}

const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";

for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}

buildMenuMap(menu);

console.log(map);





but I'm unsure how to switch the value of root



what do I set root to so that it recursively calls addLabelToMap with



'[social]', 'swipes' => '[social][swipes]', 'women' => '[social][swipes]', 'men'?



I've used root = root[element] but it's giving an error



alternative solutions would be great, but I'd like to understand why this isn't working fundamentally










share|improve this question















with an array of



['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];


I'd like to construct an map object that looks like



{
'social': {
swipes: {
women: null,
men: null
}
},
'upgrade': {
premium: null
}





const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};

const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}

const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";

for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}

buildMenuMap(menu);

console.log(map);





but I'm unsure how to switch the value of root



what do I set root to so that it recursively calls addLabelToMap with



'[social]', 'swipes' => '[social][swipes]', 'women' => '[social][swipes]', 'men'?



I've used root = root[element] but it's giving an error



alternative solutions would be great, but I'd like to understand why this isn't working fundamentally






const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};

const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}

const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";

for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}

buildMenuMap(menu);

console.log(map);





const menu = ['/social/swipes/women', '/social/likes/men', '/upgrade/premium'];
const map = {};

const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}

const buildMenuMap = menu => {
menu
// make a copy of menu
// .slice returns a copy of the original array
.slice()
// convert the string to an array by splitting the /'s
// remove the first one as it's empty
// .map returns a new array
.map(item => item.split('/').splice(1))
// iterate through each array and its elements
.forEach((element) => {
let root = map[element[0]] || "";

for (let i = 1; i < element.length; i++) {
const label = element[i];
addLabelToMap(root, label)
// set root to [root][label]
//root = ?
root = root[label];
}
});
}

buildMenuMap(menu);

console.log(map);






javascript






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 14 at 23:08

























asked Nov 14 at 21:20









totalnoob

3391629




3391629












  • Shouldn't men be in the likes object not the swipes object?
    – ibrahim mahrir
    Nov 14 at 21:22










  • I've just edited it
    – totalnoob
    Nov 14 at 21:23


















  • Shouldn't men be in the likes object not the swipes object?
    – ibrahim mahrir
    Nov 14 at 21:22










  • I've just edited it
    – totalnoob
    Nov 14 at 21:23
















Shouldn't men be in the likes object not the swipes object?
– ibrahim mahrir
Nov 14 at 21:22




Shouldn't men be in the likes object not the swipes object?
– ibrahim mahrir
Nov 14 at 21:22












I've just edited it
– totalnoob
Nov 14 at 21:23




I've just edited it
– totalnoob
Nov 14 at 21:23












6 Answers
6






active

oldest

votes

















up vote
5
down vote



accepted
+50










This problem is about creating the object and maintaining it's state while looping through input array and splitting string based upon /.



This can be accomplished using Array.reduce where we start with empty object and while looping through input we start filling it and for last word in every string we assign the value null to object property.






let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

let output = input.reduce((o, d) => {
let keys = d.split('/').filter(d => d)

keys.reduce((k, v, i) => {
k[v] = (i != keys.length - 1)
? k[v] || {}
: null

return k[v]
}, o)

return o
}, {})

console.log(output)








share|improve this answer





















  • how does let keys = d.split('/').filter(d => d) remove the empty entries?
    – totalnoob
    Nov 25 at 19:23










  • .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
    – Nitish Narang
    Nov 25 at 19:25






  • 1




    .filter(d => d) represents filter only truthy value
    – Nitish Narang
    Nov 25 at 19:26










  • Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
    – Nitish Narang
    Nov 25 at 19:27










  • clear and simple. thanks
    – totalnoob
    Nov 25 at 19:27


















up vote
3
down vote













It is as easy as:



 root = root[label];


if you change your helper function to:



 const addLabelToMap = (root, label) => {
if(!root[label]) root[label] = {};
}




I'd write it as:



 const buildMenuMap = menus => {
const root = {};

for(const menu of menus) {
const keys = menu.split("/").slice(1);
const prop = keys.pop();
const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
obj[prop] = null;
}

return root;
}





share|improve this answer























  • it didn't work when I tried it
    – totalnoob
    Nov 14 at 21:26












  • that's awesome. can you tell me why the current code doesn't work even setting root properly?
    – totalnoob
    Nov 14 at 21:31










  • @totalnoob because addLabelToMap does not go deeper into the map
    – Jonas Wilms
    Nov 14 at 21:32










  • when I use root = root[label], on the next loop root is undefined if I print it out
    – totalnoob
    Nov 14 at 21:38




















up vote
2
down vote













Use reduce instead of map. The root will be the accumulator in this case:



const buildMenuMap = menu =>
menu.reduce((root, item) => {
let parts = item.slice(1).split("/");
let lastPart = parts.pop();
let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
leaf[lastPart] = null;
return root;
}, Object.create(null));


Explanation:



For each item in the menu array, we extract the parts by first getting rid of the leading '/' (using slice(1)) and then splitting by '/'.



We then remove the lastPart from this resulting array (the last part is handled separetely from the rest).



For each remaining part in the parts array, we traverse the root array. At each level of traversing, we either return the object at that level acc[part] if it already exists, or we create and return a new one if it doesn't (acc[part] = {}).



After we get to the the last level leaf, we use the lastPart to set the value as null.



Notice that we pass Object.create(null) to reduce. Object.create(null) creates a prototypeless object so it will ba safer to use root[someKey] without having to check if someKey is an owned property or not.



Example:






const buildMenuMap = menu =>
menu.reduce((root, item) => {
let parts = item.slice(1).split("/");
let lastPart = parts.pop();
let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
leaf[lastPart] = null;
return root;
}, Object.create(null));

let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

let result = buildMenuMap(arr);

console.log(result);








share|improve this answer























  • I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
    – totalnoob
    Nov 14 at 22:33


















up vote
2
down vote













I just debugged your code to see what was wrong and I urge you to do the same. You make two (obvious) mistakes:



Firstly, In the very first iteration, here the value of map is just an empty object {}, the value of root gets initialised to "" and label is swipes.



.forEach((element) => {
let root = map[element[0]] || "";
...
root = root[label];
}


So then you get root[label] is undefined and so the new root is undefined.



Second, you are using map everywhere as it is.



const addLabelToMap = (root, label) => {
if(!map[root]) map[root] = {};
if(!map[root][label]) map[root][label] = {};
}


Instead you should be taking it as a parameter, for you to be able to do a recursion.



const addLabelToMap = (root, label) => {
if(!root[label]) root[label] = {};
}


To debug you code, create a simple HTML file with the js in the script tags and then serve it from your local machine using python -m http.server. You can then add a debug point and go through your code step by step.






share|improve this answer




























    up vote
    2
    down vote













    Try this as a holistic solution:



    const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

    const deepMerge = (target, source) => {
    // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
    for (let key of Object.keys(source)) {
    if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
    }

    // Join `target` and modified `source`
    Object.assign(target || {}, source)
    return target
    };

    const buildMenuMap = menu => {
    return menu
    .map(item => item.split('/').splice(1))

    // The `root` value is the object that we will be merging all directories into
    .reduce((root, directory) => {

    // Iterates backwards through each directory array, stacking the previous accumulated object into the current one
    const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);

    // Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
    return deepMerge(root, branch);
    }, {});
    };

    buildMenuMap(menu);


    Note: The deep merge solution was taken from @ahtcx on GitHubGist






    share|improve this answer






























      up vote
      2
      down vote













      You can simplify your code using Array.reduce, Object.keys & String.substring



      buildMenuMap



      The function takes the array as input and reduce it into an object where for each entry in array, the object is updated with corresponding hierarchy using addLabelToMap function. Each entry is converted into an array of levels (c.substring(1).split("/")).



      addLabelToMap



      The function takes 2 inputs





      • obj - the current root object / node


      • ar - array of child hierarchy


      and returns the updated object



      Logic




      • function pops the first value (let key = ar.shift()) as key and add / update in the object (obj[key] = obj[key] || {};).

      • If there is child hierarchy of current object (if(ar.length)), recursively call the function to update the object till the end (addLabelToMap(obj[key], ar)).

      • Else (no further child hierarchy), check whether the object has some hierarchy (else if(!Object.keys(obj[key]).length)) because of other entries in array. If there is no hierarchy, i.e. it is a leaf, hence, set the value to null (obj[key] = null). Note, if there will never be case where there is an entry in array like /social/swipes/men/young along with existing, the else if block can be simplified to a simple else block.

      • The object has been update, return the final updated object





      let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

      function addLabelToMap(obj, ar) {
      let key = ar.shift();
      obj[key] = obj[key] || {};
      if(ar.length) addLabelToMap(obj[key], ar);
      else if(!Object.keys(obj[key]).length) obj[key] = null;
      return obj;
      }

      function buildMenuMap(ar) {
      return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
      }

      console.log(buildMenuMap(arr));








      share|improve this answer





















        Your Answer






        StackExchange.ifUsing("editor", function () {
        StackExchange.using("externalEditor", function () {
        StackExchange.using("snippets", function () {
        StackExchange.snippets.init();
        });
        });
        }, "code-snippets");

        StackExchange.ready(function() {
        var channelOptions = {
        tags: "".split(" "),
        id: "1"
        };
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function() {
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled) {
        StackExchange.using("snippets", function() {
        createEditor();
        });
        }
        else {
        createEditor();
        }
        });

        function createEditor() {
        StackExchange.prepareEditor({
        heartbeatType: 'answer',
        convertImagesToLinks: true,
        noModals: true,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: 10,
        bindNavPrevention: true,
        postfix: "",
        imageUploader: {
        brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
        contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
        allowUrls: true
        },
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        });


        }
        });














        draft saved

        draft discarded


















        StackExchange.ready(
        function () {
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53308903%2fbuilding-a-menu-list-object-recursively-in-javascript%23new-answer', 'question_page');
        }
        );

        Post as a guest















        Required, but never shown

























        6 Answers
        6






        active

        oldest

        votes








        6 Answers
        6






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes








        up vote
        5
        down vote



        accepted
        +50










        This problem is about creating the object and maintaining it's state while looping through input array and splitting string based upon /.



        This can be accomplished using Array.reduce where we start with empty object and while looping through input we start filling it and for last word in every string we assign the value null to object property.






        let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let output = input.reduce((o, d) => {
        let keys = d.split('/').filter(d => d)

        keys.reduce((k, v, i) => {
        k[v] = (i != keys.length - 1)
        ? k[v] || {}
        : null

        return k[v]
        }, o)

        return o
        }, {})

        console.log(output)








        share|improve this answer





















        • how does let keys = d.split('/').filter(d => d) remove the empty entries?
          – totalnoob
          Nov 25 at 19:23










        • .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
          – Nitish Narang
          Nov 25 at 19:25






        • 1




          .filter(d => d) represents filter only truthy value
          – Nitish Narang
          Nov 25 at 19:26










        • Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
          – Nitish Narang
          Nov 25 at 19:27










        • clear and simple. thanks
          – totalnoob
          Nov 25 at 19:27















        up vote
        5
        down vote



        accepted
        +50










        This problem is about creating the object and maintaining it's state while looping through input array and splitting string based upon /.



        This can be accomplished using Array.reduce where we start with empty object and while looping through input we start filling it and for last word in every string we assign the value null to object property.






        let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let output = input.reduce((o, d) => {
        let keys = d.split('/').filter(d => d)

        keys.reduce((k, v, i) => {
        k[v] = (i != keys.length - 1)
        ? k[v] || {}
        : null

        return k[v]
        }, o)

        return o
        }, {})

        console.log(output)








        share|improve this answer





















        • how does let keys = d.split('/').filter(d => d) remove the empty entries?
          – totalnoob
          Nov 25 at 19:23










        • .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
          – Nitish Narang
          Nov 25 at 19:25






        • 1




          .filter(d => d) represents filter only truthy value
          – Nitish Narang
          Nov 25 at 19:26










        • Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
          – Nitish Narang
          Nov 25 at 19:27










        • clear and simple. thanks
          – totalnoob
          Nov 25 at 19:27













        up vote
        5
        down vote



        accepted
        +50







        up vote
        5
        down vote



        accepted
        +50




        +50




        This problem is about creating the object and maintaining it's state while looping through input array and splitting string based upon /.



        This can be accomplished using Array.reduce where we start with empty object and while looping through input we start filling it and for last word in every string we assign the value null to object property.






        let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let output = input.reduce((o, d) => {
        let keys = d.split('/').filter(d => d)

        keys.reduce((k, v, i) => {
        k[v] = (i != keys.length - 1)
        ? k[v] || {}
        : null

        return k[v]
        }, o)

        return o
        }, {})

        console.log(output)








        share|improve this answer












        This problem is about creating the object and maintaining it's state while looping through input array and splitting string based upon /.



        This can be accomplished using Array.reduce where we start with empty object and while looping through input we start filling it and for last word in every string we assign the value null to object property.






        let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let output = input.reduce((o, d) => {
        let keys = d.split('/').filter(d => d)

        keys.reduce((k, v, i) => {
        k[v] = (i != keys.length - 1)
        ? k[v] || {}
        : null

        return k[v]
        }, o)

        return o
        }, {})

        console.log(output)








        let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let output = input.reduce((o, d) => {
        let keys = d.split('/').filter(d => d)

        keys.reduce((k, v, i) => {
        k[v] = (i != keys.length - 1)
        ? k[v] || {}
        : null

        return k[v]
        }, o)

        return o
        }, {})

        console.log(output)





        let input = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let output = input.reduce((o, d) => {
        let keys = d.split('/').filter(d => d)

        keys.reduce((k, v, i) => {
        k[v] = (i != keys.length - 1)
        ? k[v] || {}
        : null

        return k[v]
        }, o)

        return o
        }, {})

        console.log(output)






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 23 at 20:29









        Nitish Narang

        2,850812




        2,850812












        • how does let keys = d.split('/').filter(d => d) remove the empty entries?
          – totalnoob
          Nov 25 at 19:23










        • .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
          – Nitish Narang
          Nov 25 at 19:25






        • 1




          .filter(d => d) represents filter only truthy value
          – Nitish Narang
          Nov 25 at 19:26










        • Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
          – Nitish Narang
          Nov 25 at 19:27










        • clear and simple. thanks
          – totalnoob
          Nov 25 at 19:27


















        • how does let keys = d.split('/').filter(d => d) remove the empty entries?
          – totalnoob
          Nov 25 at 19:23










        • .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
          – Nitish Narang
          Nov 25 at 19:25






        • 1




          .filter(d => d) represents filter only truthy value
          – Nitish Narang
          Nov 25 at 19:26










        • Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
          – Nitish Narang
          Nov 25 at 19:27










        • clear and simple. thanks
          – totalnoob
          Nov 25 at 19:27
















        how does let keys = d.split('/').filter(d => d) remove the empty entries?
        – totalnoob
        Nov 25 at 19:23




        how does let keys = d.split('/').filter(d => d) remove the empty entries?
        – totalnoob
        Nov 25 at 19:23












        .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
        – Nitish Narang
        Nov 25 at 19:25




        .split will split the string based on '/'. So string /social/swipes/women will become - ['', social,swipes,women] and filter will then remove falsy values and emoty string is a falsy value
        – Nitish Narang
        Nov 25 at 19:25




        1




        1




        .filter(d => d) represents filter only truthy value
        – Nitish Narang
        Nov 25 at 19:26




        .filter(d => d) represents filter only truthy value
        – Nitish Narang
        Nov 25 at 19:26












        Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
        – Nitish Narang
        Nov 25 at 19:27




        Falsy values Doc - developer.mozilla.org/en-US/docs/Glossary/Falsy
        – Nitish Narang
        Nov 25 at 19:27












        clear and simple. thanks
        – totalnoob
        Nov 25 at 19:27




        clear and simple. thanks
        – totalnoob
        Nov 25 at 19:27












        up vote
        3
        down vote













        It is as easy as:



         root = root[label];


        if you change your helper function to:



         const addLabelToMap = (root, label) => {
        if(!root[label]) root[label] = {};
        }




        I'd write it as:



         const buildMenuMap = menus => {
        const root = {};

        for(const menu of menus) {
        const keys = menu.split("/").slice(1);
        const prop = keys.pop();
        const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
        obj[prop] = null;
        }

        return root;
        }





        share|improve this answer























        • it didn't work when I tried it
          – totalnoob
          Nov 14 at 21:26












        • that's awesome. can you tell me why the current code doesn't work even setting root properly?
          – totalnoob
          Nov 14 at 21:31










        • @totalnoob because addLabelToMap does not go deeper into the map
          – Jonas Wilms
          Nov 14 at 21:32










        • when I use root = root[label], on the next loop root is undefined if I print it out
          – totalnoob
          Nov 14 at 21:38

















        up vote
        3
        down vote













        It is as easy as:



         root = root[label];


        if you change your helper function to:



         const addLabelToMap = (root, label) => {
        if(!root[label]) root[label] = {};
        }




        I'd write it as:



         const buildMenuMap = menus => {
        const root = {};

        for(const menu of menus) {
        const keys = menu.split("/").slice(1);
        const prop = keys.pop();
        const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
        obj[prop] = null;
        }

        return root;
        }





        share|improve this answer























        • it didn't work when I tried it
          – totalnoob
          Nov 14 at 21:26












        • that's awesome. can you tell me why the current code doesn't work even setting root properly?
          – totalnoob
          Nov 14 at 21:31










        • @totalnoob because addLabelToMap does not go deeper into the map
          – Jonas Wilms
          Nov 14 at 21:32










        • when I use root = root[label], on the next loop root is undefined if I print it out
          – totalnoob
          Nov 14 at 21:38















        up vote
        3
        down vote










        up vote
        3
        down vote









        It is as easy as:



         root = root[label];


        if you change your helper function to:



         const addLabelToMap = (root, label) => {
        if(!root[label]) root[label] = {};
        }




        I'd write it as:



         const buildMenuMap = menus => {
        const root = {};

        for(const menu of menus) {
        const keys = menu.split("/").slice(1);
        const prop = keys.pop();
        const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
        obj[prop] = null;
        }

        return root;
        }





        share|improve this answer














        It is as easy as:



         root = root[label];


        if you change your helper function to:



         const addLabelToMap = (root, label) => {
        if(!root[label]) root[label] = {};
        }




        I'd write it as:



         const buildMenuMap = menus => {
        const root = {};

        for(const menu of menus) {
        const keys = menu.split("/").slice(1);
        const prop = keys.pop();
        const obj = keys.reduce((curr, key) => curr[key] || (curr[key] = {}), root);
        obj[prop] = null;
        }

        return root;
        }






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 14 at 21:31

























        answered Nov 14 at 21:25









        Jonas Wilms

        53.1k42447




        53.1k42447












        • it didn't work when I tried it
          – totalnoob
          Nov 14 at 21:26












        • that's awesome. can you tell me why the current code doesn't work even setting root properly?
          – totalnoob
          Nov 14 at 21:31










        • @totalnoob because addLabelToMap does not go deeper into the map
          – Jonas Wilms
          Nov 14 at 21:32










        • when I use root = root[label], on the next loop root is undefined if I print it out
          – totalnoob
          Nov 14 at 21:38




















        • it didn't work when I tried it
          – totalnoob
          Nov 14 at 21:26












        • that's awesome. can you tell me why the current code doesn't work even setting root properly?
          – totalnoob
          Nov 14 at 21:31










        • @totalnoob because addLabelToMap does not go deeper into the map
          – Jonas Wilms
          Nov 14 at 21:32










        • when I use root = root[label], on the next loop root is undefined if I print it out
          – totalnoob
          Nov 14 at 21:38


















        it didn't work when I tried it
        – totalnoob
        Nov 14 at 21:26






        it didn't work when I tried it
        – totalnoob
        Nov 14 at 21:26














        that's awesome. can you tell me why the current code doesn't work even setting root properly?
        – totalnoob
        Nov 14 at 21:31




        that's awesome. can you tell me why the current code doesn't work even setting root properly?
        – totalnoob
        Nov 14 at 21:31












        @totalnoob because addLabelToMap does not go deeper into the map
        – Jonas Wilms
        Nov 14 at 21:32




        @totalnoob because addLabelToMap does not go deeper into the map
        – Jonas Wilms
        Nov 14 at 21:32












        when I use root = root[label], on the next loop root is undefined if I print it out
        – totalnoob
        Nov 14 at 21:38






        when I use root = root[label], on the next loop root is undefined if I print it out
        – totalnoob
        Nov 14 at 21:38












        up vote
        2
        down vote













        Use reduce instead of map. The root will be the accumulator in this case:



        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));


        Explanation:



        For each item in the menu array, we extract the parts by first getting rid of the leading '/' (using slice(1)) and then splitting by '/'.



        We then remove the lastPart from this resulting array (the last part is handled separetely from the rest).



        For each remaining part in the parts array, we traverse the root array. At each level of traversing, we either return the object at that level acc[part] if it already exists, or we create and return a new one if it doesn't (acc[part] = {}).



        After we get to the the last level leaf, we use the lastPart to set the value as null.



        Notice that we pass Object.create(null) to reduce. Object.create(null) creates a prototypeless object so it will ba safer to use root[someKey] without having to check if someKey is an owned property or not.



        Example:






        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));

        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let result = buildMenuMap(arr);

        console.log(result);








        share|improve this answer























        • I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
          – totalnoob
          Nov 14 at 22:33















        up vote
        2
        down vote













        Use reduce instead of map. The root will be the accumulator in this case:



        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));


        Explanation:



        For each item in the menu array, we extract the parts by first getting rid of the leading '/' (using slice(1)) and then splitting by '/'.



        We then remove the lastPart from this resulting array (the last part is handled separetely from the rest).



        For each remaining part in the parts array, we traverse the root array. At each level of traversing, we either return the object at that level acc[part] if it already exists, or we create and return a new one if it doesn't (acc[part] = {}).



        After we get to the the last level leaf, we use the lastPart to set the value as null.



        Notice that we pass Object.create(null) to reduce. Object.create(null) creates a prototypeless object so it will ba safer to use root[someKey] without having to check if someKey is an owned property or not.



        Example:






        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));

        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let result = buildMenuMap(arr);

        console.log(result);








        share|improve this answer























        • I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
          – totalnoob
          Nov 14 at 22:33













        up vote
        2
        down vote










        up vote
        2
        down vote









        Use reduce instead of map. The root will be the accumulator in this case:



        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));


        Explanation:



        For each item in the menu array, we extract the parts by first getting rid of the leading '/' (using slice(1)) and then splitting by '/'.



        We then remove the lastPart from this resulting array (the last part is handled separetely from the rest).



        For each remaining part in the parts array, we traverse the root array. At each level of traversing, we either return the object at that level acc[part] if it already exists, or we create and return a new one if it doesn't (acc[part] = {}).



        After we get to the the last level leaf, we use the lastPart to set the value as null.



        Notice that we pass Object.create(null) to reduce. Object.create(null) creates a prototypeless object so it will ba safer to use root[someKey] without having to check if someKey is an owned property or not.



        Example:






        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));

        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let result = buildMenuMap(arr);

        console.log(result);








        share|improve this answer














        Use reduce instead of map. The root will be the accumulator in this case:



        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));


        Explanation:



        For each item in the menu array, we extract the parts by first getting rid of the leading '/' (using slice(1)) and then splitting by '/'.



        We then remove the lastPart from this resulting array (the last part is handled separetely from the rest).



        For each remaining part in the parts array, we traverse the root array. At each level of traversing, we either return the object at that level acc[part] if it already exists, or we create and return a new one if it doesn't (acc[part] = {}).



        After we get to the the last level leaf, we use the lastPart to set the value as null.



        Notice that we pass Object.create(null) to reduce. Object.create(null) creates a prototypeless object so it will ba safer to use root[someKey] without having to check if someKey is an owned property or not.



        Example:






        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));

        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let result = buildMenuMap(arr);

        console.log(result);








        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));

        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let result = buildMenuMap(arr);

        console.log(result);





        const buildMenuMap = menu =>
        menu.reduce((root, item) => {
        let parts = item.slice(1).split("/");
        let lastPart = parts.pop();
        let leaf = parts.reduce((acc, part) => acc[part] || (acc[part] = {}), root);
        leaf[lastPart] = null;
        return root;
        }, Object.create(null));

        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

        let result = buildMenuMap(arr);

        console.log(result);






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 14 at 21:36

























        answered Nov 14 at 21:30









        ibrahim mahrir

        21.1k41746




        21.1k41746












        • I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
          – totalnoob
          Nov 14 at 22:33


















        • I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
          – totalnoob
          Nov 14 at 22:33
















        I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
        – totalnoob
        Nov 14 at 22:33




        I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
        – totalnoob
        Nov 14 at 22:33










        up vote
        2
        down vote













        I just debugged your code to see what was wrong and I urge you to do the same. You make two (obvious) mistakes:



        Firstly, In the very first iteration, here the value of map is just an empty object {}, the value of root gets initialised to "" and label is swipes.



        .forEach((element) => {
        let root = map[element[0]] || "";
        ...
        root = root[label];
        }


        So then you get root[label] is undefined and so the new root is undefined.



        Second, you are using map everywhere as it is.



        const addLabelToMap = (root, label) => {
        if(!map[root]) map[root] = {};
        if(!map[root][label]) map[root][label] = {};
        }


        Instead you should be taking it as a parameter, for you to be able to do a recursion.



        const addLabelToMap = (root, label) => {
        if(!root[label]) root[label] = {};
        }


        To debug you code, create a simple HTML file with the js in the script tags and then serve it from your local machine using python -m http.server. You can then add a debug point and go through your code step by step.






        share|improve this answer

























          up vote
          2
          down vote













          I just debugged your code to see what was wrong and I urge you to do the same. You make two (obvious) mistakes:



          Firstly, In the very first iteration, here the value of map is just an empty object {}, the value of root gets initialised to "" and label is swipes.



          .forEach((element) => {
          let root = map[element[0]] || "";
          ...
          root = root[label];
          }


          So then you get root[label] is undefined and so the new root is undefined.



          Second, you are using map everywhere as it is.



          const addLabelToMap = (root, label) => {
          if(!map[root]) map[root] = {};
          if(!map[root][label]) map[root][label] = {};
          }


          Instead you should be taking it as a parameter, for you to be able to do a recursion.



          const addLabelToMap = (root, label) => {
          if(!root[label]) root[label] = {};
          }


          To debug you code, create a simple HTML file with the js in the script tags and then serve it from your local machine using python -m http.server. You can then add a debug point and go through your code step by step.






          share|improve this answer























            up vote
            2
            down vote










            up vote
            2
            down vote









            I just debugged your code to see what was wrong and I urge you to do the same. You make two (obvious) mistakes:



            Firstly, In the very first iteration, here the value of map is just an empty object {}, the value of root gets initialised to "" and label is swipes.



            .forEach((element) => {
            let root = map[element[0]] || "";
            ...
            root = root[label];
            }


            So then you get root[label] is undefined and so the new root is undefined.



            Second, you are using map everywhere as it is.



            const addLabelToMap = (root, label) => {
            if(!map[root]) map[root] = {};
            if(!map[root][label]) map[root][label] = {};
            }


            Instead you should be taking it as a parameter, for you to be able to do a recursion.



            const addLabelToMap = (root, label) => {
            if(!root[label]) root[label] = {};
            }


            To debug you code, create a simple HTML file with the js in the script tags and then serve it from your local machine using python -m http.server. You can then add a debug point and go through your code step by step.






            share|improve this answer












            I just debugged your code to see what was wrong and I urge you to do the same. You make two (obvious) mistakes:



            Firstly, In the very first iteration, here the value of map is just an empty object {}, the value of root gets initialised to "" and label is swipes.



            .forEach((element) => {
            let root = map[element[0]] || "";
            ...
            root = root[label];
            }


            So then you get root[label] is undefined and so the new root is undefined.



            Second, you are using map everywhere as it is.



            const addLabelToMap = (root, label) => {
            if(!map[root]) map[root] = {};
            if(!map[root][label]) map[root][label] = {};
            }


            Instead you should be taking it as a parameter, for you to be able to do a recursion.



            const addLabelToMap = (root, label) => {
            if(!root[label]) root[label] = {};
            }


            To debug you code, create a simple HTML file with the js in the script tags and then serve it from your local machine using python -m http.server. You can then add a debug point and go through your code step by step.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered Nov 18 at 15:48









            TheChetan

            2,23711530




            2,23711530






















                up vote
                2
                down vote













                Try this as a holistic solution:



                const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                const deepMerge = (target, source) => {
                // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
                for (let key of Object.keys(source)) {
                if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
                }

                // Join `target` and modified `source`
                Object.assign(target || {}, source)
                return target
                };

                const buildMenuMap = menu => {
                return menu
                .map(item => item.split('/').splice(1))

                // The `root` value is the object that we will be merging all directories into
                .reduce((root, directory) => {

                // Iterates backwards through each directory array, stacking the previous accumulated object into the current one
                const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);

                // Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
                return deepMerge(root, branch);
                }, {});
                };

                buildMenuMap(menu);


                Note: The deep merge solution was taken from @ahtcx on GitHubGist






                share|improve this answer



























                  up vote
                  2
                  down vote













                  Try this as a holistic solution:



                  const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                  const deepMerge = (target, source) => {
                  // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
                  for (let key of Object.keys(source)) {
                  if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
                  }

                  // Join `target` and modified `source`
                  Object.assign(target || {}, source)
                  return target
                  };

                  const buildMenuMap = menu => {
                  return menu
                  .map(item => item.split('/').splice(1))

                  // The `root` value is the object that we will be merging all directories into
                  .reduce((root, directory) => {

                  // Iterates backwards through each directory array, stacking the previous accumulated object into the current one
                  const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);

                  // Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
                  return deepMerge(root, branch);
                  }, {});
                  };

                  buildMenuMap(menu);


                  Note: The deep merge solution was taken from @ahtcx on GitHubGist






                  share|improve this answer

























                    up vote
                    2
                    down vote










                    up vote
                    2
                    down vote









                    Try this as a holistic solution:



                    const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                    const deepMerge = (target, source) => {
                    // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
                    for (let key of Object.keys(source)) {
                    if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
                    }

                    // Join `target` and modified `source`
                    Object.assign(target || {}, source)
                    return target
                    };

                    const buildMenuMap = menu => {
                    return menu
                    .map(item => item.split('/').splice(1))

                    // The `root` value is the object that we will be merging all directories into
                    .reduce((root, directory) => {

                    // Iterates backwards through each directory array, stacking the previous accumulated object into the current one
                    const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);

                    // Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
                    return deepMerge(root, branch);
                    }, {});
                    };

                    buildMenuMap(menu);


                    Note: The deep merge solution was taken from @ahtcx on GitHubGist






                    share|improve this answer














                    Try this as a holistic solution:



                    const menu = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                    const deepMerge = (target, source) => {
                    // Iterate through `source` properties and if an `Object` set property to merge of `target` and `source` properties
                    for (let key of Object.keys(source)) {
                    if (source[key] instanceof Object && key in target) Object.assign(source[key], deepMerge(target[key], source[key]))
                    }

                    // Join `target` and modified `source`
                    Object.assign(target || {}, source)
                    return target
                    };

                    const buildMenuMap = menu => {
                    return menu
                    .map(item => item.split('/').splice(1))

                    // The `root` value is the object that we will be merging all directories into
                    .reduce((root, directory) => {

                    // Iterates backwards through each directory array, stacking the previous accumulated object into the current one
                    const branch = directory.slice().reverse().reduce((acc, cur) => { const obj = {}; obj[cur] = acc; return obj;},null);

                    // Uses the `deepMerge()` method to stitch together the accumulated `root` object with the newly constructed `branch` object.
                    return deepMerge(root, branch);
                    }, {});
                    };

                    buildMenuMap(menu);


                    Note: The deep merge solution was taken from @ahtcx on GitHubGist







                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Nov 19 at 21:02

























                    answered Nov 19 at 20:54









                    astangelo

                    458




                    458






















                        up vote
                        2
                        down vote













                        You can simplify your code using Array.reduce, Object.keys & String.substring



                        buildMenuMap



                        The function takes the array as input and reduce it into an object where for each entry in array, the object is updated with corresponding hierarchy using addLabelToMap function. Each entry is converted into an array of levels (c.substring(1).split("/")).



                        addLabelToMap



                        The function takes 2 inputs





                        • obj - the current root object / node


                        • ar - array of child hierarchy


                        and returns the updated object



                        Logic




                        • function pops the first value (let key = ar.shift()) as key and add / update in the object (obj[key] = obj[key] || {};).

                        • If there is child hierarchy of current object (if(ar.length)), recursively call the function to update the object till the end (addLabelToMap(obj[key], ar)).

                        • Else (no further child hierarchy), check whether the object has some hierarchy (else if(!Object.keys(obj[key]).length)) because of other entries in array. If there is no hierarchy, i.e. it is a leaf, hence, set the value to null (obj[key] = null). Note, if there will never be case where there is an entry in array like /social/swipes/men/young along with existing, the else if block can be simplified to a simple else block.

                        • The object has been update, return the final updated object





                        let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                        function addLabelToMap(obj, ar) {
                        let key = ar.shift();
                        obj[key] = obj[key] || {};
                        if(ar.length) addLabelToMap(obj[key], ar);
                        else if(!Object.keys(obj[key]).length) obj[key] = null;
                        return obj;
                        }

                        function buildMenuMap(ar) {
                        return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
                        }

                        console.log(buildMenuMap(arr));








                        share|improve this answer

























                          up vote
                          2
                          down vote













                          You can simplify your code using Array.reduce, Object.keys & String.substring



                          buildMenuMap



                          The function takes the array as input and reduce it into an object where for each entry in array, the object is updated with corresponding hierarchy using addLabelToMap function. Each entry is converted into an array of levels (c.substring(1).split("/")).



                          addLabelToMap



                          The function takes 2 inputs





                          • obj - the current root object / node


                          • ar - array of child hierarchy


                          and returns the updated object



                          Logic




                          • function pops the first value (let key = ar.shift()) as key and add / update in the object (obj[key] = obj[key] || {};).

                          • If there is child hierarchy of current object (if(ar.length)), recursively call the function to update the object till the end (addLabelToMap(obj[key], ar)).

                          • Else (no further child hierarchy), check whether the object has some hierarchy (else if(!Object.keys(obj[key]).length)) because of other entries in array. If there is no hierarchy, i.e. it is a leaf, hence, set the value to null (obj[key] = null). Note, if there will never be case where there is an entry in array like /social/swipes/men/young along with existing, the else if block can be simplified to a simple else block.

                          • The object has been update, return the final updated object





                          let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                          function addLabelToMap(obj, ar) {
                          let key = ar.shift();
                          obj[key] = obj[key] || {};
                          if(ar.length) addLabelToMap(obj[key], ar);
                          else if(!Object.keys(obj[key]).length) obj[key] = null;
                          return obj;
                          }

                          function buildMenuMap(ar) {
                          return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
                          }

                          console.log(buildMenuMap(arr));








                          share|improve this answer























                            up vote
                            2
                            down vote










                            up vote
                            2
                            down vote









                            You can simplify your code using Array.reduce, Object.keys & String.substring



                            buildMenuMap



                            The function takes the array as input and reduce it into an object where for each entry in array, the object is updated with corresponding hierarchy using addLabelToMap function. Each entry is converted into an array of levels (c.substring(1).split("/")).



                            addLabelToMap



                            The function takes 2 inputs





                            • obj - the current root object / node


                            • ar - array of child hierarchy


                            and returns the updated object



                            Logic




                            • function pops the first value (let key = ar.shift()) as key and add / update in the object (obj[key] = obj[key] || {};).

                            • If there is child hierarchy of current object (if(ar.length)), recursively call the function to update the object till the end (addLabelToMap(obj[key], ar)).

                            • Else (no further child hierarchy), check whether the object has some hierarchy (else if(!Object.keys(obj[key]).length)) because of other entries in array. If there is no hierarchy, i.e. it is a leaf, hence, set the value to null (obj[key] = null). Note, if there will never be case where there is an entry in array like /social/swipes/men/young along with existing, the else if block can be simplified to a simple else block.

                            • The object has been update, return the final updated object





                            let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                            function addLabelToMap(obj, ar) {
                            let key = ar.shift();
                            obj[key] = obj[key] || {};
                            if(ar.length) addLabelToMap(obj[key], ar);
                            else if(!Object.keys(obj[key]).length) obj[key] = null;
                            return obj;
                            }

                            function buildMenuMap(ar) {
                            return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
                            }

                            console.log(buildMenuMap(arr));








                            share|improve this answer












                            You can simplify your code using Array.reduce, Object.keys & String.substring



                            buildMenuMap



                            The function takes the array as input and reduce it into an object where for each entry in array, the object is updated with corresponding hierarchy using addLabelToMap function. Each entry is converted into an array of levels (c.substring(1).split("/")).



                            addLabelToMap



                            The function takes 2 inputs





                            • obj - the current root object / node


                            • ar - array of child hierarchy


                            and returns the updated object



                            Logic




                            • function pops the first value (let key = ar.shift()) as key and add / update in the object (obj[key] = obj[key] || {};).

                            • If there is child hierarchy of current object (if(ar.length)), recursively call the function to update the object till the end (addLabelToMap(obj[key], ar)).

                            • Else (no further child hierarchy), check whether the object has some hierarchy (else if(!Object.keys(obj[key]).length)) because of other entries in array. If there is no hierarchy, i.e. it is a leaf, hence, set the value to null (obj[key] = null). Note, if there will never be case where there is an entry in array like /social/swipes/men/young along with existing, the else if block can be simplified to a simple else block.

                            • The object has been update, return the final updated object





                            let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                            function addLabelToMap(obj, ar) {
                            let key = ar.shift();
                            obj[key] = obj[key] || {};
                            if(ar.length) addLabelToMap(obj[key], ar);
                            else if(!Object.keys(obj[key]).length) obj[key] = null;
                            return obj;
                            }

                            function buildMenuMap(ar) {
                            return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
                            }

                            console.log(buildMenuMap(arr));








                            let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                            function addLabelToMap(obj, ar) {
                            let key = ar.shift();
                            obj[key] = obj[key] || {};
                            if(ar.length) addLabelToMap(obj[key], ar);
                            else if(!Object.keys(obj[key]).length) obj[key] = null;
                            return obj;
                            }

                            function buildMenuMap(ar) {
                            return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
                            }

                            console.log(buildMenuMap(arr));





                            let arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];

                            function addLabelToMap(obj, ar) {
                            let key = ar.shift();
                            obj[key] = obj[key] || {};
                            if(ar.length) addLabelToMap(obj[key], ar);
                            else if(!Object.keys(obj[key]).length) obj[key] = null;
                            return obj;
                            }

                            function buildMenuMap(ar) {
                            return ar.reduce((a,c) => addLabelToMap(a,c.substring(1).split("/")), {});
                            }

                            console.log(buildMenuMap(arr));






                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Nov 22 at 15:25









                            Nikhil Aggarwal

                            23.5k32647




                            23.5k32647






























                                draft saved

                                draft discarded




















































                                Thanks for contributing an answer to Stack Overflow!


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.





                                Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                                Please pay close attention to the following guidance:


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.




                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53308903%2fbuilding-a-menu-list-object-recursively-in-javascript%23new-answer', 'question_page');
                                }
                                );

                                Post as a guest















                                Required, but never shown





















































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown

































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown







                                Popular posts from this blog

                                Feedback on college project

                                Futebolista

                                Albești (Vaslui)