@storm-g/multi-filter
v0.1.7
Published
A lightweight and simple TypeScript package to help filter arrays. Provides intelityping for filtering to help with code completions and prevent errors. Increases the readability of code when using multiple filters.
Maintainers
Readme
README
multi-filter
A lightweight and simple package for making complex array filtering easier to read and write.
It is often necessary to apply multiple checks or functions when filtering arrays. This is often approached by either
creating large and convoluted functions, or by chaining multiple arr.filter calls one after another.
These approaches are often frustrating to implement, read, and maintain. This package aims to make the process easier,
while also providing powerful typing and type-hinting.
Version: 0.1.7
View the source code on Bitbucket
Install
npm add @storm-g/multi-filterUsage
Most of the time you would only be making use of the createFilterFunc and combineFilters functions
(or their aliases).
import { createFilterFunc, combineFilters } from '@storm-g/multi-filter';Though you may want to make use of the provided types such as Operator and FilterFunc<T>.
import { FilterFunc } from '@storm-g/multi-filter/types/filtering';
import { Operator } from '@storm-g/multi-filter/types/operators';
/* etc. */Example
Say we need to filter an array arr by a number of conditions.
- for element
eofarr20 <= e.age < 30 - for
eofarre.city.namemust be inRome,London,Cape Town - for
eofarre.surname != Smith...
Usually this problem would be solved by implementing one complex filter function which performs all checks, or by chaining multiple filter calls together. one messy function
const f = (e: T): boolean => {
if (e.age < 20 || e.age >= 30) {
return false;
}
if (!['Rome', 'London', 'Cape Town'].includes(e.city.name)) {
return false;
}
if (e.surname == 'Smith') {
return false;
}
/* ... */
return true;
}
arr = arr.filter(f);chained filters
arr = arr
.filter((e: T): boolean => e.age >= 20 && e.age < 30)
.filter((e: T): boolean => ['Rome', 'London', 'Cape Town'].includes(e.city.name))
.filter((e: T): boolean => e.surname != 'Smith')
.filter(/* ... */);Instead, we can use multi-filter to neatly and intuitively write these filters as
import {combineFilters, createFilterFunc} from '@storm-g/multi-filter';
const f = combineFilters([
createFilterFunc('age', '>=', 20),
createFilterFunc('age', '<', 30),
createFilterFunc((e: T) => e.city.name, 'in', ['Rome', 'London', 'Cape Town']),
createFilterFunc('surname', '!=', 'Smith'),
/* ... */
]);
arr = arr.filter(f);AND/OR filtering
multi-filter has an additional advantage. Filters can be combined with an OR relation, meaning that an element is included in the filtered result as soon as the first filter function returns a truthy value. If we took the above example and changed it from requiring all conditions to be true to just requiring one (OR) filtering becomes even more tedious. Essentially this example would require something like:
const f = (e: T): boolean => {
if (e.age >= 20 && e.age < 30) {
return true;
}
if (['Rome', 'London', 'Cape Town'].includes(e.city.name)) {
return true;
}
if (e.surname != 'Smith') {
return true;
}
/* ... */
return false;
}Using multi-filter is easier to read and write, essentially being identical as before but with the addition of one parameter:
import {combineFilters, createFilterFunc} from '@storm-g/multi-filter';
const f = combineFilters(
[
createFilterFunc('age', '>=', 20),
createFilterFunc('age', '<', 30),
createFilterFunc((e: T) => e.city.name, 'in', ['Rome', 'London', 'Cape Town']),
createFilterFunc('surname', '!=', 'Smith'),
/* ... */
],
'OR'
);
arr = arr.filter(f);Furthermore, this allows for the creation of complex filters by nesting combineFilters calls:
// Filter array, arr, where fa(x) or fb(x) is true, and fc(x) or fd(x) are true.
const f = combineFilters(
[
combineFilters([fa, fb], 'OR'),
combineFilters([fc, fd], 'OR'),
], 'AND'
);
arr = arr.filter(f);We don't have to make use 'AND' | 'OR' exclusively. The project makes use of helpful types and even an AndOr enum.
This means the string value is not case-sensitive and in the case of 'AND' we could also use &&, AndOr.AND,
AND (if the const is imported).
import { AndOr, OR } from '@storm-g/multi-filter/types/operators';
/* all following cases are equivalent */
combineFilters([fa, fb], '||');
combineFilters([fa, fb], 'or');
combineFilters([fa, fb], 'Or');
combineFilters([fa, fb], 'Or');
combineFilters([fa, fb], AndOr.OR);
combineFilters([fa, fb], OR);Externally, you can make use of the versatility
import {isAnd, isOr, toAndOr} from '@storm-g/multi-filter/types/operators';
isOr('oR'); // returns true
isAnd('&&'); // returns true
toAndOr('aND'); // returns 'AndOr.AND'If you prefer, we can even use dedicated functions and, or which call combineFilters with the appropriate
parameter. This could further increase readability.
import {and, or} from '@storm-g/multi-filter';
// Filter array, arr, where fa(x) or fb(x) is true, and fc(x) or fd(x) are true.
const f = and(
or(fa, fb),
or(fc, fd),
);
arr = arr.filter(f);Performance note: combineFilters prioritizes readability and type-hiting etc. For small arrays, chained
arr.filtercalls are often faster.
For larger arrays, avoiding multiple passes over the data, combineFilter is
often more efficient.
Take a look at the included benchmarks. Enhancements to efficiency on smaller arrays is planned and intended to at least match chained calls.
Aliases
If the function names are a little verbose for your liking, there are shorter aliases available:
| function | alias |
|--------------------|--------------------|
| combineFilters | combine, com |
| createFilterFunc | filterFunc, ff |
