Skip to content

Instantly share code, notes, and snippets.

@tunnckoCore
Created February 26, 2024 22:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tunnckoCore/7ea7c20d50939cf20ccda2ee61555daf to your computer and use it in GitHub Desktop.
Save tunnckoCore/7ea7c20d50939cf20ccda2ee61555daf to your computer and use it in GitHub Desktop.
mongo like filters for javascript objects
function getAttribute(obj: any, key: any) {
const parts = key.split(".");
for (let _i = 0, parts_1 = parts; _i < parts_1.length; _i++) {
const k = parts_1[_i];
if (obj !== null && typeof obj === "object" && k in obj) {
obj = obj[k];
} else {
obj = undefined;
break;
}
}
return obj;
}
function exists(a: any, shouldExist: boolean): boolean {
const doesExist = a !== null && a !== undefined;
return shouldExist ? doesExist : !doesExist;
}
function isEqual(a: any, b: any): boolean {
return a === b;
}
function greaterThan(a: any, b: any): boolean {
return a > b;
}
function greaterThanOrEqual(a: any, b: any): boolean {
return a >= b;
}
function lessThan(a: any, b: any): boolean {
return a < b;
}
function lessThanOrEqual(a: any, b: any): boolean {
return a <= b;
}
function testExpression(expr: any, value: any): boolean {
if (typeof expr !== "object" || expr instanceof Date) {
if (Array.isArray(value)) {
return value.some((v) => isEqual(v, expr));
} else {
return isEqual(expr, value);
}
}
if (expr && expr._elemMatch) {
if (!Array.isArray(value)) {
return false;
} else if (!value.some((v) => matches(expr._elemMatch, v, undefined))) {
return false;
}
} else if (Array.isArray(value)) {
const _loop_1 = (key: any) => {
let _a: any;
const partialExpr = ((_a = {}), (_a[key] = expr[key]), _a);
const isTrue = value.some((v) => matches(partialExpr, v, undefined));
if (!isTrue) {
return { value: false };
}
};
// I don't understand why they designed the language this way, but I guess we'll do it the same way.
// https://docs.mongodb.com/manual/tutorial/query-array-of-documents/#combination-of-elements-satisfies-the-criteria
for (const key in expr) {
const state_1 = _loop_1(key);
if (typeof state_1 === "object") {
return state_1.value;
}
}
return true;
}
if ("_eq" in expr && !isEqual(value, expr._eq)) {
return false;
}
if ("_ne" in expr && isEqual(value, expr._ne)) {
return false;
}
if (typeof expr._exists === "boolean" && !exists(value, expr._exists)) {
return false;
}
if ("_not" in expr) {
if (typeof expr._not !== "object" || expr._not === null) {
throw new Error("_not needs a regex or a document");
}
if (testExpression(expr._not, value)) {
return false;
}
}
if (expr._gt !== undefined && !greaterThan(value, expr._gt)) {
return false;
}
if (expr._lt !== undefined && !lessThan(value, expr._lt)) {
return false;
}
if (expr._lte !== undefined && !lessThanOrEqual(value, expr._lte)) {
return false;
}
if (expr._gte !== undefined && !greaterThanOrEqual(value, expr._gte)) {
return false;
}
if (
expr._contains &&
typeof value === "string" &&
!value.includes(expr._contains)
) {
return false;
}
if (
expr._includes &&
typeof value === "string" &&
!value.includes(expr._includes)
) {
return false;
}
// starts with
if (
expr._startsWith &&
typeof value === "string" &&
!value.startsWith(expr._startsWith)
) {
return false;
}
if (
expr._endsWith &&
typeof value === "string" &&
!value.startsWith(expr._endsWith)
) {
return false;
}
if (expr._in && !expr._in.includes(value)) {
return false;
}
if (expr._nin && expr._nin.includes(value)) {
return false;
}
if (expr._regex && !new RegExp(expr._regex).test(value)) {
return false;
}
return true;
}
export function matches(
expression: any,
record: any,
extractor: any = undefined
): boolean {
if (extractor === undefined) {
extractor = getAttribute;
}
if (typeof expression !== "object" || expression instanceof Date) {
return isEqual(expression, record);
}
if (expression._and && expression._or) {
throw new Error(
'Indeterminate behavior. "_and" and "_or" operators cannot be present at the same level.'
);
}
if (Array.isArray(expression._and)) {
return expression._and.every((exp: any) => matches(exp, record, extractor));
} else if (Array.isArray(expression._or)) {
return expression._or.some((exp: any) => matches(exp, record, extractor));
} else if (expression._not) {
if (typeof expression._not !== "object") {
throw new Error("_not needs a regex or a document");
}
return !matches(expression._not, record, extractor);
} else if (!Array.isArray(record) && typeof record === "object") {
const expr = expression;
// Implicit AND operation if multiple root level keys provided
for (const key in expression) {
const value = extractor(record, key);
if (!testExpression(expr[key], value)) {
return false;
}
}
return true;
} else {
return testExpression(expression, record);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment