Kinds of operators
Table of Contents
Unary operator
An unary operator is an operator with only one operand.
Example A typical example is the logical negate operator.
[{
"!": [{"var": "some_variable", "type": {"codename": "boolean"}}]
},
{
"some_variable": true
}]
Because of the negate operator, the previous JSON should evaluate to false
.
How to define a custom Unary operator
You need to implement the trait UnaryOperator
and pass it in a MethodConf
─ exactly as in Add a Custom Operator at Evaluation time.
To implement the trait UnaryOperator
means to implement only the abstract method def unaryOperator(value: Any): Any
Example
object SomeUnaryOperator extends UnaryOperator {
def unaryOperator(value: Any): Any = ...
}
val methodConf = MethodConf(
"unary_operator_codename",
null,
Some(SomeUnaryOperator),
false,
false,
true
)
...
Reduce operator
A reduce operator reduces the elements of an array into a single result.
At each step, the reduce sequentially scans an element from the original array and takes two inputs:
- the accumulated result so far
- the current element from array
and updates the accumulated result.
How to define a Reduce operator
There is no constraint on method names to implement. A reduce operator only needs to implement a method compatible signature with array elements. Json-Logic-Scala can handle an overloaded method, as long as overloaded method is not generic.
Example:
In the following example, the two times operator has “times2” as a codename and must declare an Operator
class/object that defines the computation performed by this operator.
object Times2Operator extends Operator {
def method2Times(num1: Double, num2: Double): Double = 2 * num1 * num2
}
The operator is provided to EvaluatorLogicConf
through the MethodConf
object.
val methodConf = MethodConf("times2", "method2Times", Some(Times2Operator), true, false, false)
Make sure to give the right methodName
and ownerMethodOpt
{
"times2": [
{"var": "some_value1", "type": {"codename": "int"}},
{"var": "some_value2", "type": {"codename": "int"}}
]
}
Composition Operator
A composition operator is an operator that takes two inputs:
- an array of values
- an operator that shall be applied on the array of values
Common composition operators are: map, filter, reduce, all, some, none
How to define a Composition operator
You need to implement the trait CompositionOperator
and pass it in a MethodConf
─ exactly as in Add a Custom Operator at Evaluation time.
To implement the trait CompositionOperator
means to implement abstract methods:
def checkInputs(conditions: Array[JsonLogicCore]): Unit
composeOperator
def composeOperator( values: Array[Any], logicArr: Array[JsonLogicCore], conditionCaller: ComposeLogic, reduceLogic: EvaluatorLogic, logicOperatorToValue: Map[ComposeLogic, Map[String, Any]] ): Any
checkInputs
method
Verifies there is no error in the input conditions.
conditions
input is an array. The first element is typically the set of values that the composition operator is applied to. The second element is an operator that is applied to it.
composeOperator
method
Performs the logic behind the operator.
values
: values that operator operates on.logicArr
: array of sub-nodes this composition operator operates one. Most of the time array is of length 1.conditionCaller
: node in syntax tree whose operator is this one.reduceLogic
: evaluator that called this method. Required to pass updatedlogicOperatorToValue
recursively to sub-tree and evaluate it.logicOperatorToValue
: map of (composition_operator -> map(variable_name -> variable_value)).operated
value by operator.
Example
object OperatorMap extends CompositionOperator {
def checkInputs(conditions: Array[JsonLogicCore]): Unit = {
if (conditions.length != 2) {
val condString = conditions.mkString("[", ", ", "]")
throw new WrongNumberOfConditionsException(s"Map operator requires length of condition inputs array to be exactly 2.\nArray of conditions: $condString")
}
}
override def composeOperator(
values: Array[Any],
logicArr: Array[JsonLogicCore],
conditionCaller: ComposeLogic,
reduceLogic: EvaluatorLogic,
logicOperatorToValue: Map[ComposeLogic, Map[String, Any]]
): Any = {
val jsonLogicComposition = logicArr(0)
values.map(value => {
val newLogicOperatorToValue = logicOperatorToValue ++ Map(conditionCaller -> Map("" -> value))
reduceLogic.evaluate(jsonLogicComposition, newLogicOperatorToValue)
})
}
}
Note that compositionOperator
uses the reduceLogic
parameter to reapply the EvaluatorLogic.evaluate
method.
This is necessary in case you compose several composition operators together. In fact, you may want to combine a map operator with a filter operator. When implementing a composition operator, consider this with great care.
Global operator
A Global operator is an operator that is neither Unary, Reduce, or Composition.
Global operators are not unary, and need to evaluate all input conditions at once (the opposite of reduce operators, which evaluate element by element). Typical examples are: ifElse
, in
, merge
How to define a Global operator ?
There is no constraint on method names to implement. A global operator only needs to implement a method that takes as single input an Array. Json-Logic-Scala can handle overloaded methods, as long as the overloaded method is not generic.
Example:
In the following example, the sum of the modulo 2 operator has sum_modulo_2
as a codename. It must declare an Operator
class/object that defines the computation performed by this operator.
If there are more odd numbers in the array, this operator returns the sum of odd number. Otherwise, it returns the sum of even numbers.
object SumOfModulo2 extends Operator {
def methodSumOfModulo2(values: Array[java.lang.Integer]): java.lang.Integer = {
val numberPerModulo = arr.groupBy(_ % 2).mapValues(_.length)
if (numberPerModulo(0) > numberPerModulo(1)) values.filter(_ % 2 == 0).sum
else values.filter(_ % 2 == 1).sum
}
}
val methodConf = MethodConf(
"sum_modulo_2",
"methodSumOfModulo2",
Some(SumOfModulo2),
false,
false,
false
)