Search code examples
javascriptarraystypescriptobjectnested

Filter deep nested array based on prop value and return a Record string


I have an array of objects with unknown nesting depth (it can be 5 or even more) like this:

const routes: routeType[] = [
{
  path: '/user', visible: true, title: 'User'
},
{
  path: "/admin", visible: true, title: "Admin", children: [
    {index: true, visible: false},
    {path: "dashboard", visible: true, title: "Dashboard"},
    {path: "changelog", visible: true, title: "Changelog"},
    {path: "profile", visible: true, title: "Profile"},
    {path: "settings", visible: true, title: "Settings", children: [
        {index: true, visible: false},
        {path: "themes", visible: true, title: "Theme"}
        {path: "themes/new", visible: true, title: "New"}
      ]
    }
  ]
}]

The result I would like to obtain is a String Record composed in this way:

const resultMap: Record<string, string> = {
"/admin": "Admin",
"/admin/dashboard": "Dashboard",
"/admin/changelog": "Changelog",
"/admin/profile": "Profile",
"/admin/settings": "Settings",
"/admin/settings/themes": "Themes",
"/admin/settings/themes/new": "New"}

So:

  1. Filter all records with "true" visible prop.
  2. Build a string record composed of the path and title (each child path has the corresponding parent path).

There are many similar questions but none that match my needs. I struggled for two days but in the end I couldn't find a solution. Can you help me?

Thanks in advance!


Solution

  • A simple recursive function is your friend here. Just add the correct type annotations and you're good to go.

    function buildStringRecord(
      routes: RouteType[],
      parentPath: string = '',
      resultMap: Record<string, string> = {}
    ): Record<string, string> {
      for (const route of routes) {
        if (route.visible) {
          const fullPath = parentPath + route.path;
          if(route.title) {
            resultMap[fullPath] = route.title;
          }
    
          if (route.children) {
             buildStringRecord(route.children, fullPath + '/', resultMap);
          } 
        }
      }
    
      return resultMap;
    }
    
    const resultMap = buildStringRecord(routes);
    

    TypeScript Playground

    Working Example:

    const routes = [ { path: '/user', visible: true, title: 'User', }, { path: '/admin', visible: true, title: 'Admin', children: [ { index: true, visible: false }, { path: 'dashboard', visible: true, title: 'Dashboard' }, { path: 'changelog', visible: true, title: 'Changelog' }, { path: 'profile', visible: true, title: 'Profile' }, { path: 'settings', visible: true, title: 'Settings', children: [ { index: true, visible: false }, { path: 'themes', visible: true, title: 'Theme' }, { path: 'themes/new', visible: true, title: 'New' } ] } ] }];
    
    function buildStringRecord(
      routes,
      parentPath = "",
      resultMap = {}
    ) {
      for (const route of routes) {
        if (route.visible) {
          const fullPath = parentPath + route.path;
          if(route.title) {
            resultMap[fullPath] = route.title;
          }
    
          if (route.children) {
            buildStringRecord(route.children, fullPath + '/', resultMap);
          } 
        }
      }
    
      return resultMap;
    }
    
    const resultMap = buildStringRecord(routes);
    console.log(resultMap);