From dc9385d7c6d57692ae211ef71c03671e1b290c13 Mon Sep 17 00:00:00 2001 From: Maks Osowski Date: Mon, 16 Dec 2024 15:03:29 +0100 Subject: [PATCH] chore(jsonlogic): Modified the logic to evaluate 'and' & 'or' lazily Signed-off-by: Maks Osowski --- jsonlogic.go | 32 ++++++++++++++++++++++++++------ operation.go | 21 +++++++++++++-------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/jsonlogic.go b/jsonlogic.go index edcd9eb..b11efee 100644 --- a/jsonlogic.go +++ b/jsonlogic.go @@ -63,12 +63,14 @@ func unary(operator string, value interface{}) interface{} { return b } -func _and(values []interface{}) interface{} { +func _and(values, data interface{}) interface{} { + values = getValuesWithoutParsing(values, data) var v float64 isBoolExpression := true - for _, value := range values { + for _, value := range values.([]interface{}) { + value = parseValues(value, data) if isSlice(value) { return value } @@ -101,9 +103,11 @@ func _and(values []interface{}) interface{} { return v } -func _or(values []interface{}) interface{} { - for _, value := range values { - if isTrue(value) { +func _or(values, data interface{}) interface{} { + values = getValuesWithoutParsing(values, data) + + for _, value := range values.([]interface{}) { + if isTrue(parseValues(value, data)) { return value } } @@ -428,6 +432,22 @@ func parseValues(values, data interface{}) interface{} { return parsed } +// If values represents a map (an operation), returns the result. Otherwise returns the +// values without parsing. This means that each of the returned values might be a subtree +// of JSONLogic. +// Used in lazy evaluation of "AND" and "OR" operators +func getValuesWithoutParsing(values, data interface{}) interface{} { + if values == nil || isPrimitive(values) { + return values + } + + if isMap(values) { + return apply(values, data) + } + + return values.([]interface{}) +} + func apply(rules, data interface{}) interface{} { ruleMap := rules.(map[string]interface{}) @@ -462,7 +482,7 @@ func apply(rules, data interface{}) interface{} { return some(values, data) } - return operation(operator, parseValues(values, data), data) + return operation(operator, values, data) } // an empty-map rule should return an empty-map diff --git a/operation.go b/operation.go index 392a012..4cf4271 100644 --- a/operation.go +++ b/operation.go @@ -9,6 +9,19 @@ func AddOperator(key string, cb func(values, data interface{}) (result interface } func operation(operator string, values, data interface{}) interface{} { + // "AND" evaluates values lazily, so parseValues() is delayed until needed + if operator == "and" { + return _and(values, data) + } + + // "OR" evaluates values lazily, so parseValues() is delayed until needed + if operator == "or" { + return _or(values, data) + } + + // Parse the entire remaining tree and eval recursively for non-lazy eval operators + values = parseValues(values, data) + // Check against any custom operators for index, customOperation := range customOperators { if operator == index { @@ -66,14 +79,6 @@ func operation(operator string, values, data interface{}) interface{} { parsed := values.([]interface{}) - if operator == "and" { - return _and(parsed) - } - - if operator == "or" { - return _or(parsed) - } - if operator != "in" && len(parsed) == 1 { return unary(operator, parsed[0]) }