Creating a Navbar Menu from JSON data with Lodash

Posted on

Problem

I am trying to create a navbar menu from JSON data. Actually I have achieved it but I am looking for feedback not to call getItems twice? How can I improve my code?

Thank you

var _ = require('lodash');

let menuConfig = [
    {
        ID: 1,
        TAG: "M:A",
        PARENT_TAG: "MAIN",
        TITLE: "A Title"
    },
    {
        ID: 2,
        TAG: "AS1",
        PARENT_TAG: "M:A",
        TITLE: "A Subtitle 1"
    },
    {
        ID: 3,
        TAG: "AS2",
        PARENT_TAG: "M:A",
        TITLE: "A Subtitle 2"
    },
    {
        ID: 4,
        TAG: "AS3",
        PARENT_TAG: "M:A",
        TITLE: "A Subtitle 3"
    },
    {
        ID: 5,
        TAG: "M:B",
        PARENT_TAG: "MAIN",
        TITLE: "B Title"
    },
    {
        ID: 6,
        TAG: "BS1",
        PARENT_TAG: "M:B",
        TITLE: "B Subtitle 1"
    },
    {
        ID: 7,
        TAG: "BS2",
        PARENT_TAG: "M:B",
        TITLE: "B Subtitle 2"
    },
    {
        ID: 8,
        TAG: "M:C",
        PARENT_TAG: "MAIN",
        TITLE: "C Title"
    },
    {
        ID: 8,
        TAG: "CS1",
        PARENT_TAG: "M:C",
        TITLE: "C Subtitle 1"
    }
]

function getMenu() {
    let grouped = _.groupBy(menuConfig, "PARENT_TAG");
    let menu = getItems(grouped.MAIN, grouped);
    console.log(JSON.stringify(menu, null, 3));
}

function getItems(items, grouped) {
    let subMenu = [];
    _.forEach(items, (item) => {
        let newItem = getItem(item, grouped)
        if (newItem) {
            subMenu.push(newItem);
        }
    });
    return subMenu;
}

function getItem(item, grouped) {
    if (grouped[item.TAG]) {
        let subMenu = getItems(grouped[item.TAG], grouped);
        if (subMenu && subMenu.length) {
            return {
                title: item.TITLE,
                subMenu: subMenu
            }
        }
    } else {
        let newItem = {
            title: item.TITLE
        }
        return newItem;
    }
}

getMenu();

Output needs to be like this;

[
  {
    "title": "A Title",
    "subMenu": [
      {
        "title": "A Subtitle 1"
      },
      {
        "title": "A Subtitle 2"
      },
      {
        "title": "A Subtitle 3"
      }
    ]
  },
  {
    "title": "B Title",
    "subMenu": [
      {
        "title": "B Subtitle 1"
      },
      {
        "title": "B Subtitle 2"
      }
    ]
  },
  {
    "title": "C Title",
    "subMenu": [
      {
        "title": "C Subtitle 1"
      }
    ]
  }
]

Solution

You can use Array.prototype.reduce to group items by TAG as the key and using the relationship between PARENT_TAG and TAG.

This approach will give the expected result by running the array only once.

const menuConfig = [{
  ID: 1,TAG: "M:A", PARENT_TAG: "MAIN", TITLE: "A Title"
}, {
  ID: 2, TAG: "AS1", PARENT_TAG: "M:A", TITLE: "A Subtitle 1"
}, {
  ID: 3, TAG: "AS2", PARENT_TAG: "M:A", TITLE: "A Subtitle 2"
}, {
  ID: 4, TAG: "AS3", PARENT_TAG: "M:A", TITLE: "A Subtitle 3"
}, {
  ID: 5, TAG: "M:B", PARENT_TAG: "MAIN", TITLE: "B Title"
}, {
  ID: 6, TAG: "BS1", PARENT_TAG: "M:B", TITLE: "B Subtitle 1"
}, {
  ID: 7, TAG: "BS2", PARENT_TAG: "M:B", TITLE: "B Subtitle 2"
}, {
  ID: 8, TAG: "M:C", PARENT_TAG: "MAIN", TITLE: "C Title"
}, {
  ID: 8, TAG: "CS1", PARENT_TAG: "M:C", TITLE: "C Subtitle 1"
}]

function getMenu() {
  const menu = getItems(menuConfig, 'MAIN');
  console.log(menu);
}

function getItems(items, grandParentTag) {
  const newItems = items.reduce((modifiedObj, currentItem) => {
    const parentTag = currentItem.PARENT_TAG;
    const tag = currentItem.TAG;

    if (!modifiedObj[grandParentTag]) {
      modifiedObj[parentTag] = {};
    }

    if (!modifiedObj[grandParentTag][parentTag]) {
      modifiedObj[parentTag][tag] = {
        title: currentItem.TITLE,
        subMenu: [],
      };
    } else {
      modifiedObj[grandParentTag][parentTag].subMenu.push({
        title: currentItem.TITLE,
      });
    }

    return modifiedObj;

  }, {});

  return Object.values(newItems[grandParentTag]);
}

getMenu();

Leave a Reply

Your email address will not be published. Required fields are marked *