You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
130 lines
4.2 KiB
130 lines
4.2 KiB
/**
|
|
* ============================================================================
|
|
* JSON LOGIC EVALUATOR
|
|
* ============================================================================
|
|
*/
|
|
|
|
import type { JsonLogic } from '@/types';
|
|
|
|
/**
|
|
* Evaluador de expresiones JsonLogic
|
|
*/
|
|
export class JsonLogicEvaluator {
|
|
/**
|
|
* Evalúa una expresión JsonLogic
|
|
*/
|
|
evaluate(expression: JsonLogic, data: Record<string, any>): any {
|
|
// Primitivos
|
|
if (expression === null) return null;
|
|
if (typeof expression === 'boolean') return expression;
|
|
if (typeof expression === 'number') return expression;
|
|
if (typeof expression === 'string') return expression;
|
|
if (Array.isArray(expression)) {
|
|
return expression.map(item => this.evaluate(item, data));
|
|
}
|
|
|
|
if (typeof expression !== 'object') return expression;
|
|
|
|
// Operadores
|
|
const expr = expression as Record<string, any>;
|
|
const operator = Object.keys(expr)[0];
|
|
const args = expr[operator];
|
|
|
|
switch (operator) {
|
|
// Variables
|
|
case 'var':
|
|
return this.getVar(args, data);
|
|
|
|
// Lógicos
|
|
case '==':
|
|
case '===':
|
|
return this.evaluateAll(args, data).every((v, i, arr) =>
|
|
i === 0 || v === arr[0]
|
|
);
|
|
|
|
case '!=':
|
|
case '!==':
|
|
return this.evaluate(args[0], data) !== this.evaluate(args[1], data);
|
|
|
|
case '!':
|
|
return !this.evaluate(args, data);
|
|
|
|
case 'and':
|
|
return this.evaluateAll(args, data).every(Boolean);
|
|
|
|
case 'or':
|
|
return this.evaluateAll(args, data).some(Boolean);
|
|
|
|
// Comparación
|
|
case '>':
|
|
return this.evaluate(args[0], data) > this.evaluate(args[1], data);
|
|
|
|
case '>=':
|
|
return this.evaluate(args[0], data) >= this.evaluate(args[1], data);
|
|
|
|
case '<':
|
|
return this.evaluate(args[0], data) < this.evaluate(args[1], data);
|
|
|
|
case '<=':
|
|
return this.evaluate(args[0], data) <= this.evaluate(args[1], data);
|
|
|
|
// Condicionales
|
|
case 'if':
|
|
const [condition, thenBranch, elseBranch] = args;
|
|
return this.evaluate(condition, data)
|
|
? this.evaluate(thenBranch, data)
|
|
: this.evaluate(elseBranch !== undefined ? elseBranch : null, data);
|
|
|
|
// Arrays
|
|
case 'in':
|
|
const [item, array] = args;
|
|
const evaluatedItem = this.evaluate(item, data);
|
|
const evaluatedArray = this.evaluate(array, data);
|
|
return Array.isArray(evaluatedArray) && evaluatedArray.includes(evaluatedItem);
|
|
|
|
// Matemáticos
|
|
case '+':
|
|
return this.evaluateAll(args, data).reduce((a, b) => a + b, 0);
|
|
|
|
case '-':
|
|
const nums = this.evaluateAll(args, data);
|
|
return nums.length === 1 ? -nums[0] : nums[0] - nums[1];
|
|
|
|
case '*':
|
|
return this.evaluateAll(args, data).reduce((a, b) => a * b, 1);
|
|
|
|
case '/':
|
|
const [dividend, divisor] = this.evaluateAll(args, data);
|
|
return divisor !== 0 ? dividend / divisor : null;
|
|
|
|
case '%':
|
|
return this.evaluate(args[0], data) % this.evaluate(args[1], data);
|
|
|
|
default:
|
|
console.warn(`[JsonLogic] Unknown operator: ${operator}`);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private evaluateAll(args: any[], data: Record<string, any>): any[] {
|
|
return (Array.isArray(args) ? args : [args]).map(arg =>
|
|
this.evaluate(arg, data)
|
|
);
|
|
}
|
|
|
|
private getVar(path: string | any, data: Record<string, any>): any {
|
|
if (typeof path !== 'string') {
|
|
path = this.evaluate(path, data);
|
|
}
|
|
|
|
const parts = String(path).split('.');
|
|
let current: any = data;
|
|
|
|
for (const part of parts) {
|
|
if (current === undefined || current === null) return undefined;
|
|
current = current[part];
|
|
}
|
|
|
|
return current;
|
|
}
|
|
} |