Search code examples
javascriptlodash

How to sort groups with custom sequence?


Currently the response that is being sent back to client side looks like this:

[
  {
    "categoryName": "Orders",
    "categoryItems": [
      {
        "group": "Group B",
        "groupSettings": [
          {
            "settingName": "Group b description"
          }
        ]
      },
      {
        "group": "Group C",
        "groupSettings": [
          {
            "settingName": "Group c description"
          }
        ]
      },
      {
        "group": "Group A",
        "groupSettings": [
          {
            "settingName": "Group a description"
          }
        ]
      }
    ]
  },
  {
    "categoryName": "Notifications",
    "categoryItems": [
      {
        "group": "",
        "groupSettings": [
          {
            "settingName": "Notification setting"
          }
        ]
      }
    ]
  },
  {
    "categoryName": "Personalisation",
    "categoryItems": [
      {
        "group": "",
        "groupSettings": [
          {
            "settingName": "Personalisation setting"
          }
        ]
      }
    ]
  }
]

However, the expected layout is:

[
  {
    "categoryName": "Personalisation",
    "categoryItems": [
      {
        "group": "",
        "groupSettings": [
          {
            "settingName": "Personalisation setting"
          }
        ]
      }
    ]
  },
  {
    "categoryName": "Notifications",
    "categoryItems": [
      {
        "group": "",
        "groupSettings": [
          {
            "settingName": "Notification setting"
          }
        ]
      }
    ]
  },
  {
    "categoryName": "Orders",
    "categoryItems": [
      {
        "group": "Group A",
        "groupSettings": [
          {
            "settingName": "Group a description"
          }
        ]
      },
      {
        "group": "Group b",
        "groupSettings": [
          {
            "settingName": "Group b description"
          }
        ]
      },
      {
        "group": "Group c",
        "groupSettings": [
          {
            "settingName": "Group c description"
          }
        ]
      }
    ]
  }
]

How do I sort the categoryName to match the order that I expected? I am thinking about matching the sort order to an array of object with categoryName and sequence properties, but I am stuck at the code.

My current code is as below:

const groupedData = _.chain(allData)
      .groupBy('sectionName')
      .map((allData, sectionName) => ({
        categoryName: sectionName,
        categorySettings: _.chain(allData)
          .groupBy('group')
          .map((groupSettings, group) => ({
            group: group,
            groupSettings: _.chain(groupSettings)
              .sortBy('ordering')
              .groupBy('title')
              .map((titleSettings, title) => ({
                settingName: title,
                settingDescription: titleSettings[0].description,
                settingInputs: _.map(titleSettings, ({name, value, inputSetting}) => ({
                  inputName: name,
                  inputValue: value,
                  inputConfig: inputSetting,
                })),
              }))
              .value(),
          }))
          .value(),
      }))
      .value();

Solution

  • You can create a mapping of categoryName and groupOrder to the required order i.e. index and then use that mapping to sort the data.

    I have reused your code. Considering you are using TS, i have made it typesafe too:

    //define an enum for checking during compile time
    enum CategoryName {
      Personalisation = "Personalisation",
      Notifications = "Notifications",
      Orders = "Orders",
    }
    
    //sort order for both categoryName and group
    const categoryOrder: Record<CategoryName, number> = {
      [CategoryName.Personalisation]: 0,
      [CategoryName.Notifications]: 1,
      [CategoryName.Orders]: 2,
    };
    
    //this will give you control on the group order, but if you want to generalize the sorting in alphabetical order, check the next solution
    const groupOrder: Record<string, number> = {
      "Group A": 0,
      "Group B": 1,
      "Group C": 2,
    };
    
    const sortedGroupedData = _.chain(allData)
      .groupBy('sectionName')
      .map((allData, sectionName) => ({
        categoryName: sectionName as CategoryName,
        categoryItems: _.chain(allData)
          .groupBy('group')
          .map((groupSettings, group) => ({
            group: group,
            groupSettings: _.chain(groupSettings)
              .sortBy('ordering')
              .map(({ name }) => ({
                settingName: name,
              }))
              .value(),
          }))
          .sortBy(groupItem => groupOrder[groupItem.group])
          .value(),
      }))
      .sortBy(item => categoryOrder[item.categoryName])
      .value();
    
    console.log(sortedGroupedData);
    

    (or) if you want to sort the groups generally in alphabetical order

    enum CategoryName {
      Personalisation = "Personalisation",
      Notifications = "Notifications",
      Orders = "Orders",
    }
    
    //define the sort order for categoryName
    const categoryOrder: Record<CategoryName, number> = {
      [CategoryName.Personalisation]: 0,
      [CategoryName.Notifications]: 1,
      [CategoryName.Orders]: 2,
    };
    
    const sortedGroupedData = _.chain(allData)
      .groupBy('sectionName')
      .map((allData, sectionName) => ({
        categoryName: sectionName as CategoryName,
        categoryItems: _.chain(allData)
          .groupBy('group')
          .map((groupSettings, group) => ({
            group: group,
            groupSettings: _.chain(groupSettings)
              .sortBy('ordering')
              .map(({ name }) => ({
                settingName: name,
              }))
              .value(),
          }))
          .sortBy(groupItem => groupItem.group.toLowerCase()) //groups sorted alphabetically
          .value(),
      }))
      .sortBy(item => categoryOrder[item.categoryName])
      .value();
    
    console.log(sortedGroupedData);
    

    You can use whichever you suits your purpose better