0

Here's the array that I got, and my purpose is to filter this whole array of objects and to return an array of object with founded name ( even if it's located in the nested deepest level of children's). For example, If I am filtering by "Model8", the return value of my function has to be = [{ 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: [] },
];

I've come up with 2 options :

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)

But the main problem right here is that for some reason I can't "access 'res' before initialization"

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;
}

And here, I can't get lower than first iterated object of an array. The max depth is the object with "Model3" name property.

1 Answer 1

1

We can write a generic function that collects all nested values matching the predicate supplied and then do the name search atop it, like this:

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))

We could pass any predicate here. We can to a case-insensitive comparison, or a substring search, or a combination. But that's irrelevant to the actual traversal of the tree, so it's useful to have it separated into its own function.

If you really don't like calling like findByName ('Model8') (arr) (which I prefer these days, but some don't like), we could rewrite like this:

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

findByName (arr, 'Model8')

If we wanted, we could go one step further and pass a function to decide how the children of a node are found. Here we would just pass (x) => x .children, but not every structure we want to use necessarily uses the name "children" for this. That version is left as an exercise for the reader. ;-)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.