@mcabreradev/filter
Version:
Filters an array based on the provided expression, a helper for complex array filtering
207 lines (206 loc) • 7.99 kB
JavaScript
"use strict";
/**
* Filters an array based on the provided expression.
*
* @template T - The type of elements in the array.
* @param {T[]} array - The array to filter.
* @param {unknown} expression - The expression used for filtering.
* @returns {T[]} - The filtered array.
* @throws {Error} - If the input is not an array.
*/
Object.defineProperty(exports, "__esModule", { value: true });
// Constants
var TYPES = {
BOOLEAN: 'boolean',
NULL: 'null',
NUMBER: 'number',
STRING: 'string',
OBJECT: 'object',
FUNCTION: 'function',
UNDEFINED: 'undefined',
};
var ANY_PROPERTY_KEY = '$';
// Type Checking Functions
var isType = function (type) {
return function (value) {
return typeof value === type;
};
};
var isObject = isType(TYPES.OBJECT);
var isFunction = isType(TYPES.FUNCTION);
var isString = isType(TYPES.STRING);
var isUndefined = isType(TYPES.UNDEFINED);
// Other Helper Functions
var lowercase = function (string) { return string.toLowerCase(); };
var equals = function (a, b) { return a === b; };
var getTypeForFilter = function (value) { return (value === null ? TYPES.NULL : typeof value); };
var hasCustomToString = function (obj) {
return isFunction(obj.toString) &&
obj.toString !== Object.prototype.toString;
};
// Main Filter Function
var filter = function (array, expression) {
if (!Array.isArray(array)) {
throw new Error("Expected array but received: ".concat(array));
}
var expressionType = getTypeForFilter(expression);
var matchAgainstAnyProp = [
TYPES.BOOLEAN,
TYPES.NULL,
TYPES.NUMBER,
TYPES.STRING,
].includes(expressionType);
var predicateFn = createPredicateFn(expression, equals, ANY_PROPERTY_KEY, matchAgainstAnyProp);
return array.filter(predicateFn);
};
// Predicate Function Creation
var createPredicateFn = function (expression, comparator, anyPropertyKey, matchAgainstAnyProp) {
if (comparator === void 0) { comparator = equals; }
var shouldMatchPrimitives = isObject(expression) && anyPropertyKey in expression;
var shouldNegate = false;
if (isString(expression)) {
if (expression.includes('%')) {
var regex_1 = new RegExp('^' + expression.replace(/%/g, '.*') + '$', 'i');
return function (item) {
for (var key in item) {
if (regex_1.test(item[key])) {
return true;
}
}
return false;
};
}
else if (expression.includes('_')) {
var regex_2 = new RegExp('^' + expression.replace(/_/g, '.') + '$', 'i');
return function (item) {
for (var key in item) {
if (regex_2.test(item[key])) {
return true;
}
}
return false;
};
}
else if (expression.charAt(0) === '!') {
expression = expression.slice(1);
shouldNegate = true;
}
else {
return function (item) {
return Object.values(item).some(function (value) { return value === expression; });
};
}
}
if (isObject(expression)) {
return function (item) {
for (var key in expression) {
var expr = expression[key];
var shouldNegate_1 = false;
if (isString(expr) && expr.charAt(0) === '!') {
expr = expr.slice(1);
shouldNegate_1 = true;
}
if (isString(expr) && expr.includes('%')) {
var regex = new RegExp('^' + expr.replace(/%/g, '.*') + '$', 'i');
if (shouldNegate_1 ? regex.test(item[key]) : !regex.test(item[key])) {
return false;
}
}
else if (isString(expr) && expr.includes('_')) {
var regex = new RegExp('^' + expr.replace(/_/g, '.') + '$', 'i');
if (shouldNegate_1 ? regex.test(item[key]) : !regex.test(item[key])) {
return false;
}
}
else if (shouldNegate_1 ? item[key] === expr : item[key] !== expr) {
return false;
}
}
return true;
};
}
if (isFunction(expression)) {
return expression;
}
if (isFunction(comparator)) {
comparator = function (actual, expected) {
if (isUndefined(actual))
return false;
if (actual === null || expected === null)
return actual === expected;
if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual)))
return false;
actual = lowercase('' + actual);
expected = lowercase('' + expected);
return actual.indexOf(expected) !== -1;
};
}
return function (item) {
if (shouldMatchPrimitives && !isObject(item)) {
return deepCompare(item, expression[anyPropertyKey], comparator, anyPropertyKey, false);
}
else if (shouldNegate) {
return !deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
}
return deepCompare(item, expression, comparator, anyPropertyKey, matchAgainstAnyProp);
};
};
// Deep Comparison
function deepCompare(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject) {
var actualType = getTypeForFilter(actual);
var expectedType = getTypeForFilter(expected);
if (expectedType === TYPES.STRING && expected.charAt(0) === '!') {
return !deepCompare(actual, expected.substring(1), comparator, anyPropertyKey, matchAgainstAnyProp);
}
if (Array.isArray(actual)) {
return actual.some(function (item) {
return deepCompare(item, expected, comparator, anyPropertyKey, matchAgainstAnyProp);
});
}
if (actualType === TYPES.OBJECT) {
return compareObjects(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject || false, // Fix: Ensure dontMatchWholeObject is always of type boolean
expectedType);
}
if (actualType === TYPES.FUNCTION) {
return false;
}
return comparator(actual, expected);
}
// Object Comparison
function compareObjects(actual, expected, comparator, anyPropertyKey, matchAgainstAnyProp, dontMatchWholeObject, expectedType) {
if (matchAgainstAnyProp) {
return compareAgainstAnyProperty(actual, expected, comparator, anyPropertyKey, dontMatchWholeObject);
}
if (expectedType === TYPES.OBJECT) {
return compareAllProperties(actual, expected, comparator, anyPropertyKey);
}
return comparator(actual, expected);
}
// Property Comparison
function compareAgainstAnyProperty(actual, expected, comparator, anyPropertyKey, dontMatchWholeObject) {
for (var key in actual) {
if (key.charAt &&
key.charAt(0) !== ANY_PROPERTY_KEY &&
deepCompare(actual[key], expected, comparator, anyPropertyKey, true)) {
return true;
}
}
return dontMatchWholeObject
? false
: deepCompare(actual, expected, comparator, anyPropertyKey, false);
}
function compareAllProperties(actual, expected, comparator, anyPropertyKey) {
for (var key in expected) {
var expectedVal = expected[key];
if (isFunction(expectedVal) || isUndefined(expectedVal)) {
continue;
}
var matchAnyProperty = key === anyPropertyKey;
var actualVal = matchAnyProperty ? actual : actual[key];
if (!deepCompare(actualVal, expectedVal, comparator, anyPropertyKey, matchAnyProperty, matchAnyProperty)) {
return false;
}
}
return true;
}
exports.default = filter;