Home > Net >  Recursion Solution for nested menu options using Typescript
Recursion Solution for nested menu options using Typescript

Time:08-12

I have a menu application which provides a number of options available in the menu. The options objects is very simple. It has an optionId and a type describing what type of option it is. In addition the option has a long description but I omitted it to keep things simple. The tricky part of the issue is that options can have nested option info, which provides more detail of the item. Here is a full description of the menu type, i.e. menu and the options. The JSON is processed and stored by optionId as key and Option as value in a MenuResult object.

        type MenuInfo = {
          menuId: string,
          menuType: string,
          options: Array<Option>
       }

       type Option = {
         optionId: string,
         type: string,
         options?: Array<Option>
     }

     
      type MenuResult ={
         menuId: string,
         menuType: string,
         options: Map<string, Option>
       }

Here is an example of the JSON reflecting the elements of the menu.

       {
         "menuId": "britanny-tavern-menu",
         "menuType": "drinks",
         "options": [
        {
         "optionId": "beverages",
         "type": "hot-options-1",
         "options": [
          {
           "optionId": "herbal",
           "type": "exotic",
           "options": [
            {
            "optionId": "green-tea",
            "type": "medicinal"
            },
            {
             "options": [
             {
              "optionId": "continental-brand",
              "type": "loose-leaves"
             },
             {
              "optionId": "asian-brand",
              "type": "tea-bags"
             }
           ]
         },
        {
          "optionId": "darjeeling",
          "type": "tea-bags"
        }
      ]
    }
  ]
}

] }

Unfortunately my attempt to store the data did not work. I struggle with recursion. Here is my attempt:

function makeMenuResult(menuJson: string){
        const menu = JSON.parse(menuJson);
        const menuObj = {} as MenuResult;
        menuObj.menuId = menu.menuId;
        menuObj.menuType = menu.menuType;
        const optionsMap = new Map<string, Option>();
        for(const optionItem of menu.options){
            const option:Option = {
                optionId: optionItem.optionId,
                type: optionItem.type
            }
            optionsMap.set(option.optionId, option);
            if(optionItem.options && optionItem.options.length > 0){
                for(const addOptionItem of optionItem.options){
                     const map = additionalOptionInfo(addOptionItem);
                     optionItem.options = map;
                }
            }

        }
    }

    function additionalOptionInfo(option: Option){
        const optionsMap = new Map<string, Option>();
        for(const optionItem of option.options){
            optionsMap.set(optionItem.optionId, optionItem);
        }
        return optionsMap;
    }

Please help, I don't know where to add the additional recursive step.

CodePudding user response:

In case you want to flatten your nested options into the Map of MenuResult.options (i.e. have all options in a single Map), then you can use a recursive function to flatten the array, then convert it into a Map:

function flattenOptions(options: Array<Option>) {
  const result: Array<Option> = [];
  
  for (const option of options) {
    // Add the current option
    result.push(option);
    // Add potentially nested options recursively
    result.push(...(flattenOptions(option.options ?? [])));
  }

  return result;
}

// Convert the top level
function makeMenuResult(menuJson: string){
  const menu = JSON.parse(menuJson) as MenuInfo;
  // Flatten and convert the options into a Map
  const optionsMap = new Map<string, Option>();
  for (const option of flattenOptions(menu.options)) {
    optionsMap.set(option.optionId, option);
  }
  const menuObj: MenuResult = {
    menuId: menu.menuId,
    menuType: menu.menuType,
    options: optionsMap,
  }
  return menuObj;
}

Playground Link


On the other hand, if you rather want to convert the MenuInfo type (which has potentially nested array of Option's) into a MenuResult with also potentially nested options, but stored as Map's instead of arrays, then you also need an extra type to reflect the converted Option that uses such Map type, as already done for MenuInfo and MenuResult:

type MenuOption = {
  optionId: string;
  type: string;
  options?: Map<string, MenuOption>;
};

// Adjust MenuResult to use that type:
type MenuResult = {
  menuId: string,
  menuType: string,
  options: Map<string, MenuOption>
};

Then with these adjusted types, you can perform a recursive conversion, e.g.:

function optionsJson2map(options: Array<Option>) {
  const optionsMap = new Map<string, MenuOption>();
  for (const option of options) {
    // Convert Option to MenuOption
    const menuOption: MenuOption = {
      optionid: option.optionId,
      type: option.option.type,
    };
    // Recursive part
    if (option.options) {
      menuOption.options = optionsJson2map(option.options);
    }
    optionsMap.set(option.optionId, menuOption);
  }
  return optionsMap;
}

// Convert the top level
function makeMenuResult(menuJson: string){
  const menu = JSON.parse(menuJson) as MenuInfo;
  const menuObj: MenuResult = {
    menuId: menu.menuId,
    menuType: menu.menuType,
    options: optionsJson2map(menu.options),
  }
  return menuObj;
}
  • Related