Last active
February 15, 2021 10:10
-
-
Save xxzefgh/81f0629ce595de22f9ee4a52f4284951 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
enum QuerySelectors { | |
Eq = "$eq", | |
Gt = "$gt", | |
Gte = "$gte", | |
Lt = "$lt", | |
Lte = "$lte", | |
Ne = "$ne" | |
} | |
enum LogicalOperators { | |
And = "$and", | |
// Not = "$not", | |
Nor = "$nor", | |
Or = "$or" | |
} | |
type ComparableValue = string | number | boolean | null; | |
type Variant1 = { | |
[key: string]: | |
| ComparableValue | |
| { [selector in QuerySelectors]?: ComparableValue }; | |
}; | |
type Variant2 = { [operator in LogicalOperators]?: Array<Variant1 | Variant2> }; | |
type FilterQuery = Variant1 | Variant2; | |
const querySelectors: string[] = Object.values(QuerySelectors); | |
const logicalOperators: string[] = Object.values(LogicalOperators); | |
function applyImplicitAnd(query: FilterQuery): FilterQuery { | |
return { | |
$and: [query] | |
}; | |
} | |
function filterCollection<T>( | |
query: FilterQuery, | |
items: Array<Record<string, T>> | |
): Array<Record<string, T>> { | |
const filterFn = filterItem(applyImplicitAnd(query)); | |
return items.filter(item => filterFn(item)); | |
} | |
function filterItem(query: FilterQuery) { | |
return function filterItemInner(item: Record<string, any>): boolean { | |
return filterItemRecursive(query, item); | |
}; | |
} | |
function filterItemRecursive( | |
query: FilterQuery, | |
item: Record<string, any> | |
): boolean { | |
const fieldsOrOps = Object.keys(query); | |
return fieldsOrOps.every(fieldOrOp => { | |
if (logicalOperators.includes(fieldOrOp)) { | |
const opQuery: FilterQuery[] = query[fieldOrOp]; | |
switch (fieldOrOp as LogicalOperators) { | |
case LogicalOperators.And: { | |
return opQuery.reduce( | |
(result, q) => result && filterItemRecursive(q, item), | |
true | |
); | |
} | |
case LogicalOperators.Nor: { | |
return opQuery.reduce( | |
(result, q) => result && !filterItemRecursive(q, item), | |
true | |
); | |
} | |
case LogicalOperators.Or: { | |
return !!opQuery.find(q => filterItemRecursive(q, item)); | |
} | |
} | |
} else { | |
if (isObject(query[fieldOrOp])) { | |
const fieldOps = Object.keys(query[fieldOrOp]); | |
return fieldOps.every(fieldOp => { | |
const compareTo = query[fieldOrOp][fieldOp]; | |
switch (fieldOp) { | |
case QuerySelectors.Eq: { | |
return item[fieldOrOp] === compareTo; | |
} | |
case QuerySelectors.Gt: { | |
return item[fieldOrOp] > compareTo; | |
} | |
case QuerySelectors.Gte: { | |
return item[fieldOrOp] >= compareTo; | |
} | |
case QuerySelectors.Lt: { | |
return item[fieldOrOp] < compareTo; | |
} | |
case QuerySelectors.Lte: { | |
return item[fieldOrOp] <= compareTo; | |
} | |
case QuerySelectors.Ne: { | |
return item[fieldOrOp] !== compareTo; | |
} | |
} | |
}); | |
} else { | |
return item[fieldOrOp] === query[fieldOrOp]; | |
} | |
} | |
}); | |
} | |
function isObject(value: unknown): value is object { | |
return typeof value === "object" && value !== null; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment