Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build a menu list object recursively in JavaScript?

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.

like image 303
totalnoob Avatar asked Nov 14 '18 21:11

totalnoob


2 Answers

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)
like image 99
Nitish Narang Avatar answered Oct 07 '22 04:10

Nitish Narang


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;
}
like image 21
Jonas Wilms Avatar answered Oct 07 '22 03:10

Jonas Wilms