UNPKG

@mcabreradev/filter

Version:

Filters an array based on the provided expression, a helper for complex array filtering

207 lines (206 loc) 7.99 kB
"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;