How to build a menu list object recursively in JavaScript?












14














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




















  • 1




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










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
















14














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




















  • 1




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










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














14












14








14


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 arrays ecmascript-6 javascript-objects arrow-functions






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 9 '18 at 1:01









Bharata

8,09661131




8,09661131










asked Nov 14 '18 at 21:20









totalnoobtotalnoob

3211631




3211631








  • 1




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










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














  • 1




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










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








1




1




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




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












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




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












8 Answers
8






active

oldest

votes


















12





+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 '18 at 19:23






  • 3




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










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










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










  • you're most welcome @totalnoob
    – Nitish Narang
    Nov 25 '18 at 19:27



















6














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 '18 at 21:26












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










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










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





















6














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 '18 at 22:33



















6














You can solve this also with a recursive function in a concise way like this:






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

const makeObj = (arr, obj={}) => {
let prop = arr.shift()
prop in obj ? null : Object.assign(obj, {[prop]: {}})
arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
return obj
}

input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

console.log(obj)





The idea is for each of the paths to pass them to a makeObj function which will decorate an object with the paths recursively until it reaches the end of the path array. This is another alternative to the common Array.reduce approach.






share|improve this answer



















  • 1




    I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
    – totalnoob
    Dec 9 '18 at 6:22



















5














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





























    5














    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































      5














      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





























        5





        +50










        Citate from bounty description:
        The current answers do not contain enough detail.




        I think you do not understand how it works in current answers. Because of this I will provide you two solutions: one alternative solution and one expanded solution with Array.reduce() function.



        Alternative solution with for loops



        The explanation of code see in the code comments.






        var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
        result = {};

        //if you want to have it shorter you can write for(var i = arr.length; i--;) too
        for(var i = 0; i < arr.length; i++)
        {
        var parts = arr[i].slice(1).split('/'),
        //if we have already one object then we take it. If not the we create new one:
        curObj = result[parts[0]] = result[parts[0]] || {};

        for(var k = 1; k < parts.length; k++)
        {
        //if we have next part
        if(parts[k+1])
        //if we have already one object then we take it. If not the we create new one:
        curObj[parts[k]] = curObj[parts[k]] || {};
        //if we do not have next part
        else curObj[parts[k]] = null;
        //or if-else block in one line:
        //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

        //link to next object:
        curObj = curObj[parts[k]];
        }
        }

        console.log(JSON.stringify(result, null, 4));





        But if you did not understand it then see this code:






        var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
        result = {},
        parts = arr[2].slice(1).split('/'),
        curObj = result[parts[0]] = {};

        curObj[parts[1]] = parts[1+1] ? {} : null;
        console.log(JSON.stringify(result, null, 4));





        Expanded solution with Array.reduce()



        In this solution I use the code from user Nitish Narang in expanded version with some explanation in comments and console output – so yuo can see in console what the code does. My recommendation: if you do not understand the code with arrow functions then write it full with normal functions and appropriate variable names which explain themselves. We (humans) need some pictures to imagine all things. If we have only some short variable names then it is difficalt to imagine und undestand all this. I have also a little bit «shorted» his code.






        var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
        var result = arr.reduce(function(acc0, curVal, curIdx)
        {
        console.log('n' + curIdx + ': ------------n'
        + JSON.stringify(acc0, null, 4));

        var keys = curVal.slice(1).split('/');
        keys.reduce(function(acc1, currentValue, currentIndex)
        {
        acc1[currentValue] = keys[currentIndex+1]
        ? acc1[currentValue] || {}
        : null;

        return acc1[currentValue]
        }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

        return acc0
        }, {}); // {} - initialValue is empty object


        console.log('n------result------n'
        + JSON.stringify(result, null, 4));








        share|improve this answer

















        • 1




          what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
          – totalnoob
          Dec 9 '18 at 3:16










        • @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
          – Bharata
          Dec 9 '18 at 9:55











        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',
        autoActivateHeartbeat: false,
        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%2fhow-to-build-a-menu-list-object-recursively-in-javascript%23new-answer', 'question_page');
        }
        );

        Post as a guest















        Required, but never shown

























        8 Answers
        8






        active

        oldest

        votes








        8 Answers
        8






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes









        12





        +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 '18 at 19:23






        • 3




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










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










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










        • you're most welcome @totalnoob
          – Nitish Narang
          Nov 25 '18 at 19:27
















        12





        +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 '18 at 19:23






        • 3




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










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










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










        • you're most welcome @totalnoob
          – Nitish Narang
          Nov 25 '18 at 19:27














        12





        +50







        12





        +50



        12




        +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 '18 at 20:29









        Nitish NarangNitish Narang

        2,948815




        2,948815












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






        • 3




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










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










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










        • you're most welcome @totalnoob
          – Nitish Narang
          Nov 25 '18 at 19:27


















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






        • 3




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










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










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










        • you're most welcome @totalnoob
          – Nitish Narang
          Nov 25 '18 at 19:27
















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




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




        3




        3




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




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












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




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












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




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












        you're most welcome @totalnoob
        – Nitish Narang
        Nov 25 '18 at 19:27




        you're most welcome @totalnoob
        – Nitish Narang
        Nov 25 '18 at 19:27













        6














        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 '18 at 21:26












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










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










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


















        6














        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 '18 at 21:26












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










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










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
















        6












        6








        6






        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 '18 at 21:31

























        answered Nov 14 '18 at 21:25









        Jonas WilmsJonas Wilms

        55.9k42851




        55.9k42851












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












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










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










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




















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












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










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










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


















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






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














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




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












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




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












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






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













        6














        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 '18 at 22:33
















        6














        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 '18 at 22:33














        6












        6








        6






        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 '18 at 21:36

























        answered Nov 14 '18 at 21:30









        ibrahim mahriribrahim mahrir

        21.8k41847




        21.8k41847












        • I like this. thanks. can you explain why the original code doesn't work or how I can fix it?
          – totalnoob
          Nov 14 '18 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 '18 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 '18 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 '18 at 22:33











        6














        You can solve this also with a recursive function in a concise way like this:






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

        const makeObj = (arr, obj={}) => {
        let prop = arr.shift()
        prop in obj ? null : Object.assign(obj, {[prop]: {}})
        arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
        return obj
        }

        input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

        console.log(obj)





        The idea is for each of the paths to pass them to a makeObj function which will decorate an object with the paths recursively until it reaches the end of the path array. This is another alternative to the common Array.reduce approach.






        share|improve this answer



















        • 1




          I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
          – totalnoob
          Dec 9 '18 at 6:22
















        6














        You can solve this also with a recursive function in a concise way like this:






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

        const makeObj = (arr, obj={}) => {
        let prop = arr.shift()
        prop in obj ? null : Object.assign(obj, {[prop]: {}})
        arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
        return obj
        }

        input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

        console.log(obj)





        The idea is for each of the paths to pass them to a makeObj function which will decorate an object with the paths recursively until it reaches the end of the path array. This is another alternative to the common Array.reduce approach.






        share|improve this answer



















        • 1




          I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
          – totalnoob
          Dec 9 '18 at 6:22














        6












        6








        6






        You can solve this also with a recursive function in a concise way like this:






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

        const makeObj = (arr, obj={}) => {
        let prop = arr.shift()
        prop in obj ? null : Object.assign(obj, {[prop]: {}})
        arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
        return obj
        }

        input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

        console.log(obj)





        The idea is for each of the paths to pass them to a makeObj function which will decorate an object with the paths recursively until it reaches the end of the path array. This is another alternative to the common Array.reduce approach.






        share|improve this answer














        You can solve this also with a recursive function in a concise way like this:






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

        const makeObj = (arr, obj={}) => {
        let prop = arr.shift()
        prop in obj ? null : Object.assign(obj, {[prop]: {}})
        arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
        return obj
        }

        input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

        console.log(obj)





        The idea is for each of the paths to pass them to a makeObj function which will decorate an object with the paths recursively until it reaches the end of the path array. This is another alternative to the common Array.reduce approach.






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

        const makeObj = (arr, obj={}) => {
        let prop = arr.shift()
        prop in obj ? null : Object.assign(obj, {[prop]: {}})
        arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
        return obj
        }

        input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

        console.log(obj)





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

        const makeObj = (arr, obj={}) => {
        let prop = arr.shift()
        prop in obj ? null : Object.assign(obj, {[prop]: {}})
        arr.length ? makeObj(arr, obj[prop]) : obj[prop] = null
        return obj
        }

        input.forEach(x => makeObj(x.split('/').filter(Boolean), obj))

        console.log(obj)






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Dec 9 '18 at 6:15

























        answered Dec 9 '18 at 5:42









        AkrionAkrion

        9,43211224




        9,43211224








        • 1




          I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
          – totalnoob
          Dec 9 '18 at 6:22














        • 1




          I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
          – totalnoob
          Dec 9 '18 at 6:22








        1




        1




        I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
        – totalnoob
        Dec 9 '18 at 6:22




        I like this one. I was only starting out with Array.reduce so the other solutions wasn't as easy to understand.
        – totalnoob
        Dec 9 '18 at 6:22











        5














        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


























          5














          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
























            5












            5








            5






            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 '18 at 15:48









            TheChetanTheChetan

            2,30611631




            2,30611631























                5














                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




























                  5














                  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


























                    5












                    5








                    5






                    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 '18 at 21:02

























                    answered Nov 19 '18 at 20:54









                    astangeloastangelo

                    858




                    858























                        5














                        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


























                          5














                          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
























                            5












                            5








                            5






                            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 '18 at 15:25









                            Nikhil AggarwalNikhil Aggarwal

                            23.7k32747




                            23.7k32747























                                5





                                +50










                                Citate from bounty description:
                                The current answers do not contain enough detail.




                                I think you do not understand how it works in current answers. Because of this I will provide you two solutions: one alternative solution and one expanded solution with Array.reduce() function.



                                Alternative solution with for loops



                                The explanation of code see in the code comments.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {};

                                //if you want to have it shorter you can write for(var i = arr.length; i--;) too
                                for(var i = 0; i < arr.length; i++)
                                {
                                var parts = arr[i].slice(1).split('/'),
                                //if we have already one object then we take it. If not the we create new one:
                                curObj = result[parts[0]] = result[parts[0]] || {};

                                for(var k = 1; k < parts.length; k++)
                                {
                                //if we have next part
                                if(parts[k+1])
                                //if we have already one object then we take it. If not the we create new one:
                                curObj[parts[k]] = curObj[parts[k]] || {};
                                //if we do not have next part
                                else curObj[parts[k]] = null;
                                //or if-else block in one line:
                                //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

                                //link to next object:
                                curObj = curObj[parts[k]];
                                }
                                }

                                console.log(JSON.stringify(result, null, 4));





                                But if you did not understand it then see this code:






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {},
                                parts = arr[2].slice(1).split('/'),
                                curObj = result[parts[0]] = {};

                                curObj[parts[1]] = parts[1+1] ? {} : null;
                                console.log(JSON.stringify(result, null, 4));





                                Expanded solution with Array.reduce()



                                In this solution I use the code from user Nitish Narang in expanded version with some explanation in comments and console output – so yuo can see in console what the code does. My recommendation: if you do not understand the code with arrow functions then write it full with normal functions and appropriate variable names which explain themselves. We (humans) need some pictures to imagine all things. If we have only some short variable names then it is difficalt to imagine und undestand all this. I have also a little bit «shorted» his code.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
                                var result = arr.reduce(function(acc0, curVal, curIdx)
                                {
                                console.log('n' + curIdx + ': ------------n'
                                + JSON.stringify(acc0, null, 4));

                                var keys = curVal.slice(1).split('/');
                                keys.reduce(function(acc1, currentValue, currentIndex)
                                {
                                acc1[currentValue] = keys[currentIndex+1]
                                ? acc1[currentValue] || {}
                                : null;

                                return acc1[currentValue]
                                }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

                                return acc0
                                }, {}); // {} - initialValue is empty object


                                console.log('n------result------n'
                                + JSON.stringify(result, null, 4));








                                share|improve this answer

















                                • 1




                                  what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
                                  – totalnoob
                                  Dec 9 '18 at 3:16










                                • @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
                                  – Bharata
                                  Dec 9 '18 at 9:55
















                                5





                                +50










                                Citate from bounty description:
                                The current answers do not contain enough detail.




                                I think you do not understand how it works in current answers. Because of this I will provide you two solutions: one alternative solution and one expanded solution with Array.reduce() function.



                                Alternative solution with for loops



                                The explanation of code see in the code comments.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {};

                                //if you want to have it shorter you can write for(var i = arr.length; i--;) too
                                for(var i = 0; i < arr.length; i++)
                                {
                                var parts = arr[i].slice(1).split('/'),
                                //if we have already one object then we take it. If not the we create new one:
                                curObj = result[parts[0]] = result[parts[0]] || {};

                                for(var k = 1; k < parts.length; k++)
                                {
                                //if we have next part
                                if(parts[k+1])
                                //if we have already one object then we take it. If not the we create new one:
                                curObj[parts[k]] = curObj[parts[k]] || {};
                                //if we do not have next part
                                else curObj[parts[k]] = null;
                                //or if-else block in one line:
                                //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

                                //link to next object:
                                curObj = curObj[parts[k]];
                                }
                                }

                                console.log(JSON.stringify(result, null, 4));





                                But if you did not understand it then see this code:






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {},
                                parts = arr[2].slice(1).split('/'),
                                curObj = result[parts[0]] = {};

                                curObj[parts[1]] = parts[1+1] ? {} : null;
                                console.log(JSON.stringify(result, null, 4));





                                Expanded solution with Array.reduce()



                                In this solution I use the code from user Nitish Narang in expanded version with some explanation in comments and console output – so yuo can see in console what the code does. My recommendation: if you do not understand the code with arrow functions then write it full with normal functions and appropriate variable names which explain themselves. We (humans) need some pictures to imagine all things. If we have only some short variable names then it is difficalt to imagine und undestand all this. I have also a little bit «shorted» his code.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
                                var result = arr.reduce(function(acc0, curVal, curIdx)
                                {
                                console.log('n' + curIdx + ': ------------n'
                                + JSON.stringify(acc0, null, 4));

                                var keys = curVal.slice(1).split('/');
                                keys.reduce(function(acc1, currentValue, currentIndex)
                                {
                                acc1[currentValue] = keys[currentIndex+1]
                                ? acc1[currentValue] || {}
                                : null;

                                return acc1[currentValue]
                                }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

                                return acc0
                                }, {}); // {} - initialValue is empty object


                                console.log('n------result------n'
                                + JSON.stringify(result, null, 4));








                                share|improve this answer

















                                • 1




                                  what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
                                  – totalnoob
                                  Dec 9 '18 at 3:16










                                • @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
                                  – Bharata
                                  Dec 9 '18 at 9:55














                                5





                                +50







                                5





                                +50



                                5




                                +50





                                Citate from bounty description:
                                The current answers do not contain enough detail.




                                I think you do not understand how it works in current answers. Because of this I will provide you two solutions: one alternative solution and one expanded solution with Array.reduce() function.



                                Alternative solution with for loops



                                The explanation of code see in the code comments.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {};

                                //if you want to have it shorter you can write for(var i = arr.length; i--;) too
                                for(var i = 0; i < arr.length; i++)
                                {
                                var parts = arr[i].slice(1).split('/'),
                                //if we have already one object then we take it. If not the we create new one:
                                curObj = result[parts[0]] = result[parts[0]] || {};

                                for(var k = 1; k < parts.length; k++)
                                {
                                //if we have next part
                                if(parts[k+1])
                                //if we have already one object then we take it. If not the we create new one:
                                curObj[parts[k]] = curObj[parts[k]] || {};
                                //if we do not have next part
                                else curObj[parts[k]] = null;
                                //or if-else block in one line:
                                //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

                                //link to next object:
                                curObj = curObj[parts[k]];
                                }
                                }

                                console.log(JSON.stringify(result, null, 4));





                                But if you did not understand it then see this code:






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {},
                                parts = arr[2].slice(1).split('/'),
                                curObj = result[parts[0]] = {};

                                curObj[parts[1]] = parts[1+1] ? {} : null;
                                console.log(JSON.stringify(result, null, 4));





                                Expanded solution with Array.reduce()



                                In this solution I use the code from user Nitish Narang in expanded version with some explanation in comments and console output – so yuo can see in console what the code does. My recommendation: if you do not understand the code with arrow functions then write it full with normal functions and appropriate variable names which explain themselves. We (humans) need some pictures to imagine all things. If we have only some short variable names then it is difficalt to imagine und undestand all this. I have also a little bit «shorted» his code.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
                                var result = arr.reduce(function(acc0, curVal, curIdx)
                                {
                                console.log('n' + curIdx + ': ------------n'
                                + JSON.stringify(acc0, null, 4));

                                var keys = curVal.slice(1).split('/');
                                keys.reduce(function(acc1, currentValue, currentIndex)
                                {
                                acc1[currentValue] = keys[currentIndex+1]
                                ? acc1[currentValue] || {}
                                : null;

                                return acc1[currentValue]
                                }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

                                return acc0
                                }, {}); // {} - initialValue is empty object


                                console.log('n------result------n'
                                + JSON.stringify(result, null, 4));








                                share|improve this answer













                                Citate from bounty description:
                                The current answers do not contain enough detail.




                                I think you do not understand how it works in current answers. Because of this I will provide you two solutions: one alternative solution and one expanded solution with Array.reduce() function.



                                Alternative solution with for loops



                                The explanation of code see in the code comments.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {};

                                //if you want to have it shorter you can write for(var i = arr.length; i--;) too
                                for(var i = 0; i < arr.length; i++)
                                {
                                var parts = arr[i].slice(1).split('/'),
                                //if we have already one object then we take it. If not the we create new one:
                                curObj = result[parts[0]] = result[parts[0]] || {};

                                for(var k = 1; k < parts.length; k++)
                                {
                                //if we have next part
                                if(parts[k+1])
                                //if we have already one object then we take it. If not the we create new one:
                                curObj[parts[k]] = curObj[parts[k]] || {};
                                //if we do not have next part
                                else curObj[parts[k]] = null;
                                //or if-else block in one line:
                                //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

                                //link to next object:
                                curObj = curObj[parts[k]];
                                }
                                }

                                console.log(JSON.stringify(result, null, 4));





                                But if you did not understand it then see this code:






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {},
                                parts = arr[2].slice(1).split('/'),
                                curObj = result[parts[0]] = {};

                                curObj[parts[1]] = parts[1+1] ? {} : null;
                                console.log(JSON.stringify(result, null, 4));





                                Expanded solution with Array.reduce()



                                In this solution I use the code from user Nitish Narang in expanded version with some explanation in comments and console output – so yuo can see in console what the code does. My recommendation: if you do not understand the code with arrow functions then write it full with normal functions and appropriate variable names which explain themselves. We (humans) need some pictures to imagine all things. If we have only some short variable names then it is difficalt to imagine und undestand all this. I have also a little bit «shorted» his code.






                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
                                var result = arr.reduce(function(acc0, curVal, curIdx)
                                {
                                console.log('n' + curIdx + ': ------------n'
                                + JSON.stringify(acc0, null, 4));

                                var keys = curVal.slice(1).split('/');
                                keys.reduce(function(acc1, currentValue, currentIndex)
                                {
                                acc1[currentValue] = keys[currentIndex+1]
                                ? acc1[currentValue] || {}
                                : null;

                                return acc1[currentValue]
                                }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

                                return acc0
                                }, {}); // {} - initialValue is empty object


                                console.log('n------result------n'
                                + JSON.stringify(result, null, 4));








                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {};

                                //if you want to have it shorter you can write for(var i = arr.length; i--;) too
                                for(var i = 0; i < arr.length; i++)
                                {
                                var parts = arr[i].slice(1).split('/'),
                                //if we have already one object then we take it. If not the we create new one:
                                curObj = result[parts[0]] = result[parts[0]] || {};

                                for(var k = 1; k < parts.length; k++)
                                {
                                //if we have next part
                                if(parts[k+1])
                                //if we have already one object then we take it. If not the we create new one:
                                curObj[parts[k]] = curObj[parts[k]] || {};
                                //if we do not have next part
                                else curObj[parts[k]] = null;
                                //or if-else block in one line:
                                //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

                                //link to next object:
                                curObj = curObj[parts[k]];
                                }
                                }

                                console.log(JSON.stringify(result, null, 4));





                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {};

                                //if you want to have it shorter you can write for(var i = arr.length; i--;) too
                                for(var i = 0; i < arr.length; i++)
                                {
                                var parts = arr[i].slice(1).split('/'),
                                //if we have already one object then we take it. If not the we create new one:
                                curObj = result[parts[0]] = result[parts[0]] || {};

                                for(var k = 1; k < parts.length; k++)
                                {
                                //if we have next part
                                if(parts[k+1])
                                //if we have already one object then we take it. If not the we create new one:
                                curObj[parts[k]] = curObj[parts[k]] || {};
                                //if we do not have next part
                                else curObj[parts[k]] = null;
                                //or if-else block in one line:
                                //curObj[parts[k]] = parts[k+1] ? (curObj[parts[k]] || {}) : null;

                                //link to next object:
                                curObj = curObj[parts[k]];
                                }
                                }

                                console.log(JSON.stringify(result, null, 4));





                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {},
                                parts = arr[2].slice(1).split('/'),
                                curObj = result[parts[0]] = {};

                                curObj[parts[1]] = parts[1+1] ? {} : null;
                                console.log(JSON.stringify(result, null, 4));





                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'],
                                result = {},
                                parts = arr[2].slice(1).split('/'),
                                curObj = result[parts[0]] = {};

                                curObj[parts[1]] = parts[1+1] ? {} : null;
                                console.log(JSON.stringify(result, null, 4));





                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
                                var result = arr.reduce(function(acc0, curVal, curIdx)
                                {
                                console.log('n' + curIdx + ': ------------n'
                                + JSON.stringify(acc0, null, 4));

                                var keys = curVal.slice(1).split('/');
                                keys.reduce(function(acc1, currentValue, currentIndex)
                                {
                                acc1[currentValue] = keys[currentIndex+1]
                                ? acc1[currentValue] || {}
                                : null;

                                return acc1[currentValue]
                                }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

                                return acc0
                                }, {}); // {} - initialValue is empty object


                                console.log('n------result------n'
                                + JSON.stringify(result, null, 4));





                                var arr = ['/social/swipes/women', '/social/swipes/men', '/upgrade/premium'];
                                var result = arr.reduce(function(acc0, curVal, curIdx)
                                {
                                console.log('n' + curIdx + ': ------------n'
                                + JSON.stringify(acc0, null, 4));

                                var keys = curVal.slice(1).split('/');
                                keys.reduce(function(acc1, currentValue, currentIndex)
                                {
                                acc1[currentValue] = keys[currentIndex+1]
                                ? acc1[currentValue] || {}
                                : null;

                                return acc1[currentValue]
                                }, acc0); //acc0 - initialValue is the same object, but it is empty only in first cycle

                                return acc0
                                }, {}); // {} - initialValue is empty object


                                console.log('n------result------n'
                                + JSON.stringify(result, null, 4));






                                share|improve this answer












                                share|improve this answer



                                share|improve this answer










                                answered Dec 9 '18 at 0:51









                                BharataBharata

                                8,09661131




                                8,09661131








                                • 1




                                  what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
                                  – totalnoob
                                  Dec 9 '18 at 3:16










                                • @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
                                  – Bharata
                                  Dec 9 '18 at 9:55














                                • 1




                                  what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
                                  – totalnoob
                                  Dec 9 '18 at 3:16










                                • @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
                                  – Bharata
                                  Dec 9 '18 at 9:55








                                1




                                1




                                what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
                                – totalnoob
                                Dec 9 '18 at 3:16




                                what does curObj = result[parts[0]] = result[parts[0]] || {}; do?
                                – totalnoob
                                Dec 9 '18 at 3:16












                                @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
                                – Bharata
                                Dec 9 '18 at 9:55




                                @totalnoob, if we have in parts[0] for example the value upgrade (it could be also social) then we ask: is result["upgrade"] != "undefined" and if yes, then we take the same value: result["upgrade"] = result["upgrade"]. But if not then we create a new object: result["upgrade"] = {}. And the same value get our new variable curObj. In a litle bit expanded version we could write it like follows: if(result[parts[0]] != "undefined") result[parts[0]] = result[parts[0]]; else result[parts[0]] = {}; var curObj = result[parts[0]]; I hope you can understand it now?
                                – Bharata
                                Dec 9 '18 at 9:55


















                                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.




                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53308903%2fhow-to-build-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

                                MongoDB - Not Authorized To Execute Command

                                How to fix TextFormField cause rebuild widget in Flutter

                                in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith