Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to exclude nested array elements that don’t contain matches #690

Closed
vwkd opened this issue Sep 27, 2022 · 6 comments
Closed

Option to exclude nested array elements that don’t contain matches #690

vwkd opened this issue Sep 27, 2022 · 6 comments

Comments

@vwkd
Copy link

vwkd commented Sep 27, 2022

Description

Fuse is conveniently capable of nested search into objects with arrays as properties.

[
  {
    "a": [
      {
        "b": ["lorem", "ipsum"]
      },
      {
        "b": ["dolor", "sit"]
      }
    ]
  },
  {
    "a": [
      {
        "b": ["amen", "consectetur"]
      },
      {
        "b": ["adipiscing", "elit"]
      }
    ]
  }
]

Given the key a.b, searching for lorem yields the first element of the root array.

[
  {
    "item": {
      "a": [
        {
          "b": ["lorem", "ipsum"]
        },
        {
          "b": ["dolor", "sit"]
        }
      ]
    },
    "refIndex": 0
  }
]

However, when listing the results of a search it’s sometimes desirable to filter out any elements of any subarrays that do not contain any matches.

[
  {
    "item": {
      "a": [
        {
          "b": ["lorem"]
        }
      ]
    },
    "refIndex": 0
  }
]

Fuse currently can not filter out non-matching elements of subarrays. Also the option includeMatches only gives the matched leaf values instead of the elements of the root array.

[
  {
    "item": {
      "a": [
        {
          "b": ["lorem", "ipsum"]
        },
        {
          "b": ["dolor", "sit"]
        }
      ]
    },
    "refIndex": 0,
    "matches": [
      {
        "indices": [[0, 4]],
        "value": "lorem",
        "key": "a.b",
        "refIndex": 0
      }
    ]
  }
]

See also inactive #186 which gained some support.

Describe the solution you'd like

An option includeNonMatchingArrayElements: false that allows to filter out non-matching subarray elements.

Describe alternatives you've considered

Filtering the results manually by comparing them to the matches. However, the general solution for arbitrarily deeply nested objects is non-trivial, StackOverflow only has a non-general solution. Also it duplicates a lot of search-related code in the app which shouldn’t be necessary.

@vwkd vwkd added the feature label Sep 27, 2022
@vwkd vwkd changed the title Option to exclude nested array elements that didn’t match Option to exclude nested array elements that don’t contain matches Sep 28, 2022
@vwkd
Copy link
Author

vwkd commented Oct 11, 2022

As a temporary workaround, I'm using the following function to filter Fuse's search results and return new filtered item properties.

It depends on the utilities deepFilter and deepMerge.

It also depends on a fuse_options object with includeMatches: true and keys with an array of string keys.

function filterResults(results) {

  let resultsNew = [];

  for (const result of results) {
    
    const { item, matches } = result;
    
    let resultNew = {};
    
    // note: group multiple matches per key, otherwise would become separate entries in highest ancestor array if processes separately
    for (const key of fuse_options.keys) {
      
      const matchesForKey = matches.filter(({ key: k }) => k == key);
      
      if (matchesForKey.length) {
        const valuesForKey = matchesForKey.map(({ value }) => value);
     
        const resultForKey = deepFilter(item, key.split("."), obj => valuesForKey.some(value => obj === value));
         
        resultNew = deepMerge(resultNew, resultForKey);
      }
    }
    
    resultsNew.push(resultNew);
  }
  
  return resultsNew;
}

It's certainly not ideal but it seems to get the job done.

Also you might need to adapt it slightly depending on your specific results, like if you only want to filter the matches for certain keys.

@github-actions
Copy link

github-actions bot commented Feb 9, 2023

This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 30 days

@CavalcanteLeo
Copy link

hey @vwkd but by using filter, doesn't it lose all power of fuse.js? such as text proximity?

Have you figured out how to solve this using fuse?

Thanks

@e-lobo
Copy link

e-lobo commented Sep 15, 2023

anything here?

@briavicenti
Copy link

Our use case would really benefit from this! We're also having to filter ourselves to get this working. @krisk, any chance we can reopen this issue?

@briavicenti
Copy link

In case it's helpful to anyone in the interim, here's our solution to filter results of the following shape:

type NestedOption<T> = {
  data: T;
  children: T[];
}

const exampleData = [{
  data: {
    value: "fruit",
    label: "Fruit",
  },
  children: [
    {
      value: "raspberry",
      label: "Black Raspberry",
    },
    {
      value: "orange",
      label: "Blood Orange",
    },
  ],
}]

The filterOptionArrayForSearch param is just a small wrapper around Fuse that searches for the query text via the label key.

const flattenedDataForSearch = options.flatMap(({ data, children }) => [
  data,
  ...children,
]);

const flatMatches = new Set(
  filterOptionArrayForSearch({
    options,
    query,
  })
);

/**
 * If a parent matches the search query, we want to show all of its children. Otherwise,
 * filter the children down to only those that match the search query.
 */
const parentsWithFilteredChildren = options.map(({ data, children }) => ({
  data,
  children: flatMatches.has(data)
    ? children
    : children.filter((child) => flatMatches.has(child)),
}));

/**
 * Filter the options to those that have matching parent data or at least one matching child.
 */
const result = parentsWithFilteredChildren.filter(({ data, children }) => {
  return flatMatches.has(data) || children.length > 0;
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants