Массив рекурсивной фильтрации объектов с вложенностью по определенному значению свойства

#javascript #arrays #recursion #filter

Вопрос:

Вот массив, который я получил, и моя цель-отфильтровать весь этот массив объектов и вернуть массив объектов с именем ( даже если он расположен на самом глубоком вложенном уровне детей). Например, если я фильтрую по «Model8», возвращаемое значение моей функции должно быть = [{ name: "Model8", type: "file", path: "/path/to/file" }]

 const arr = [
  {
    name: "Model",
    type: "directory",
    path: "/path/to/folder",
    children: [
      {
        name: "Model1",
        type: "file",
        path: "/path/to/file",
        children: [
          {
            name: "Model2",
            type: "file",
            path: "/path/to/file",
            children: [
              {
                name: "Model3",
                type: "file",
                path: "/path/to/file",
                children: [
                  { name: "Model4", type: "file", path: "/path/to/file" },
                ],
              },
            ],
          },
        ],
      },
    ],
  },
  {
    name: "Inventory",
    type: "directory",
    path: "/path/to/folder",
    children: [{ name: "inventory.yaml", type: "file", path: "/path/to/file" }],
  },
  {
    name: "UI",
    type: "directory",
    path: "/path/to/folder",
    children: [
      { name: "elements", type: "directory", path: "/path/to/file" },
      { name: "viewmodel", type: "directory", path: "/path/to/file" },
      { name: "i18n", type: "directory", path: "/path/to/file" },
      {
        name: "index.template.html",
        type: "file",
        path: "/path/to/file",
        children: [
          {
            name: "Model5",
            type: "file",
            path: "/path/to/file",
            children: [
              {
                name: "Model6",
                type: "file",
                path: "/path/to/file",
                children: [
                  {
                    name: "Model7",
                    type: "file",
                    path: "/path/to/file",
                    children: [
                      { name: "Model8", type: "file", path: "/path/to/file" },
                    ],
                  },
                ],
              },
            ],
          },
        ],
      },
    ],
  },
  { name: "DeviceConnector", type: "directory", children: [] },
];
 

Я придумал 2 варианта :

1)

 function searchFilter(searchVal,arr) {
        const res = arr.filter(function filteredList(el) {
          if (el.children) {
              el.children = el.children.filter(filteredList);
          }
          if (el.name.toLowerCase().includes(searchVal.toLowerCase())) return true;
          return res;
      }); 
    }
    
    searchFilter("Model8",arr)
 

Но главная проблема здесь в том, что по какой-то причине я не могу «получить доступ к ‘res’ до инициализации».

2)

 function searchFilter(arr, name) {
  const searchItem = arr.find((i) => i.name === name);
  if (!searchItem) {
    return arr.filter((i) => searchFilter(i.children, name));
  }
  return searchitem;
}
 

И здесь я не могу опуститься ниже, чем первый итерационный объект массива. Максимальная глубина-это объект со свойством имени «Model3».

Ответ №1:

Мы можем написать универсальную функцию, которая собирает все вложенные значения, соответствующие предоставленному предикату, а затем выполнить поиск по имени поверх него, например так:

 const collect = (pred) => (xs = []) => 
  xs .flatMap (x => [
    ... (pred (x) ? [x] : []),
    ... collect (pred) (x .children)
  ])

const findByName = (target) => 
  collect (({name}) => name == target)

const arr = [{name: "Model", type: "directory", path: "/path/to/folder", children: [{name: "Model1", type: "file", path: "/path/to/file", children: [{name: "Model2", type: "file", path: "/path/to/file", children: [{name: "Model3", type: "file", path: "/path/to/file", children: [{name: "Model4", type: "file", path: "/path/to/file"}]}]}]}]}, {name: "Inventory", type: "directory", path: "/path/to/folder", children: [{name: "inventory.yaml", type: "file", path: "/path/to/file"}]}, {name: "UI", type: "directory", path: "/path/to/folder", children: [{name: "elements", type: "directory", path: "/path/to/file"}, {name: "viewmodel", type: "directory", path: "/path/to/file"}, {name: "i18n", type: "directory", path: "/path/to/file"}, {name: "index.template.html", type: "file", path: "/path/to/file", children: [{name: "Model5", type: "file", path: "/path/to/file", children: [{name: "Model6", type: "file", path: "/path/to/file", children: [{name: "Model7", type: "file", path: "/path/to/file", children: [{name: "Model8", type: "file", path: "/path/to/file"}]}]}]}]}]}, {name: "DeviceConnector", type: "directory", children: []}]

console .log (findByName ('Model8') (arr)) 

Мы могли бы передать здесь любой предикат. Мы можем использовать сравнение без учета регистра, или поиск подстрок, или комбинацию. Но это не имеет отношения к фактическому обходу дерева, поэтому полезно разделить его на отдельные функции.

Если вам действительно не нравится звонить подобным findByName ('Model8') (arr) образом (что я предпочитаю в наши дни, но некоторым не нравится), мы могли бы переписать так:

 const findByName = (xs, target) => 
  collect (({name}) => name == target) (xs)

findByName (arr, 'Model8')
 

Если бы мы хотели, мы могли бы сделать еще один шаг вперед и передать функцию, чтобы решить, как найти дочерние элементы узла. Здесь мы бы просто прошли (x) => x .children , но не каждая структура, которую мы хотим использовать, обязательно использует "children" для этого имя. Эта версия оставлена в качестве упражнения для читателя. 😉