Operators Overview
datalogic-rs provides 84 built-in operators organized into logical categories. In the Rust crate, 33 baseline operators are always available in the default build (default = []); a further 29 canonical operators are enabled by opt-in Cargo features, twenty tensor-marshalling operators sit behind the tensor feature, and two flagd-compatible operators (fractional, sem_ver) sit behind the flagd feature. Every language binding (WASM, Node, Python, Go, JVM, .NET, PHP) ships with all operator features enabled, so outside Rust the full set is available with no configuration. Counts are by canonical operator: var and ?: are accepted as input aliases of val and if, and match is an alias of switch, so the aliases are not counted separately. Each page in this section gives an operator’s syntax, argument counts, examples and edge cases.
Operator Categories
| Category | Operators | Description |
|---|---|---|
| Variable Access | val (alias var), exists | Access and check data |
| Comparison | ==, ===, !=, !==, >, >=, <, <= | Compare values |
| Logical | !, !!, and, or | Boolean logic |
| Arithmetic | +, -, *, /, %, max, min, abs, ceil, floor | Math operations |
| Control Flow | if (alias ?:), ??, switch (alias match), type | Conditional branching |
| String | cat, substr, in, length, starts_with, ends_with, upper, lower, trim, split | String manipulation |
| Array | merge, filter, map, reduce, all, some, none, sort, slice, group_by, distinct | Array operations |
| Object | keys, values, entries | Object take-apart |
| DateTime | datetime, timestamp, parse_date, format_date, date_diff, now | Date and time |
| Missing Values | missing, missing_some | Check for missing data |
| Error Handling | try, throw | Exception handling |
| Tensor | tensor, zeros, full, scatter, rle_expand, one_hot, stack, concat, unstack, reshape, transpose, pad, crop, cast, normalize, argmax, gather, to_list, shape, dtype | Marshalling JSON to and from typed n-dimensional buffers; requires features = ["tensor"] |
| flagd-Compat | fractional, sem_ver | Feature-flag targeting (OpenFeature flagd spec); requires features = ["flagd"] |
Which operators need which Cargo feature
The split applies to the Rust crate. The default build (default = []) compiles in the baseline only, and the all-operators feature turns on every family below at once. A rule that names an operator your build left out still compiles: Engine::compile treats the unknown key like an unregistered custom operator, and evaluating the rule fails with an InvalidOperator error naming the operator. Engine::check and Engine::compile_checked report the unknown operator before the rule runs. In templating mode the engine echoes an unknown key as data instead (see Structured Objects). Every language binding enables all operator features, so the full set is available there.
| Cargo feature | Operators |
|---|---|
| baseline (always on) | val/var, ==, ===, !=, !==, >, >=, <, <=, !, !!, and, or, if/?:, +, -, *, /, %, max, min, cat, substr, in, merge, filter, map, reduce, all, some, none, missing, missing_some |
datetime | datetime, timestamp, parse_date, format_date, date_diff, now |
ext-string | length, starts_with, ends_with, upper, lower, trim, split |
ext-array | sort, slice, group_by, distinct |
ext-object | keys, values, entries |
ext-control | exists, ??, switch/match, type |
error-handling | try, throw |
ext-math | abs, ceil, floor |
tensor | tensor, zeros, full, scatter, rle_expand, one_hot, stack, concat, unstack, reshape, transpose, pad, crop, cast, normalize, argmax, gather, to_list, shape, dtype |
flagd | fractional, sem_ver |
tensor-half adds no operators; it lets the element-wise tensor operators handle f16 / bf16.
The table above is generated from the engine’s operator table
(UPDATE_OPERATORS_JSON=1 cargo test -p datalogic-rs --all-features --test operators_json_test regenerates it, together with
operators.json).
Operator families
Each row of the table is one operator family: Core (the baseline),
DateTime, ExtString, ExtArray, ExtObject, ExtControl,
ErrorHandling, ExtMath, Tensor and Flagd, in table order. Since
5.8.0 you can also keep one engine to a subset of the families its build
compiled in:
use datalogic_rs::{Engine, Family};
// The JSONLogic core plus the string extensions.
let engine = Engine::builder().with_families([Family::ExtString]).build();
On that engine a family you left out behaves like one the build never
compiled, and Core is always present. The bindings take the same option
as a list of family names (families, Families, withFamilies or
WithFamilies). See
Operator Families.
Operator catalogue
Engine::operators() describes every built-in operator an engine has:
canonical name and aliases, family and Cargo feature, the argument counts
it reads (min_args, and max_args or no upper limit), whether it reads
the data context, its effect (pure, clock, throws, catches), its
cost class, and which argument runs under a pushed frame.
operators.json is that catalogue for an
all-operators build, and the bindings return the same JSON from their
operators call.
Engine::builtin_operator_names()
lists the names alone, aliases included. Both follow the engine’s
families, so use them instead of a hand-copied list when tooling needs to
know what an engine evaluates.
Rule Analysis
covers the catalogue API and its fields.
An argument past max_args is never evaluated. Most operators ignore it;
length, the tensor operators and the JSONLogic iterators (filter,
map, reduce, all, some, none) reject it, and sem_ver returns
null.
Operator Syntax
All operators follow the JSONLogic format:
{ "operator": [arg1, arg2, ...] }
Some operators accept a single argument without an array:
{ "var": "name" }
// Equivalent to:
{ "var": ["name"] }
Lazy Evaluation
These operators evaluate an argument only when they need it:
and: stops at the first falsy valueor: stops at the first truthy valueif(and its alias?:): evaluates only the matching branch??: stops at the first value that is notnullswitch(and its aliasmatch): evaluates only the matching case’s result, or the defaulttry: evaluates a fallback arm only if every arm before it raised an error
This matters when a later operand could raise an error, or is expensive:
{
"and": [
{ "var": "denominator" },
{ "/": [100, { "var": "denominator" }] }
]
}
If denominator is missing or 0, and returns that falsy value and never evaluates the division (which would throw a NaN error for an integer zero divisor); with { "denominator": 4 } the result is 25. Under the default configuration var returns null for a missing path, so plain property access such as { "var": "user.profile.name" } needs no guard; an engine configured with MissingVar::Error raises VariableNotFound for it instead (see Missing Variables).
Type Coercion
Loose vs Strict
==and!=perform type coercion===and!==require exact type match
{ "==": [1, "1"] } // true (loose)
{ "===": [1, "1"] } // false (strict)
Numeric Coercion
Arithmetic operators attempt to convert values to numbers:
{ "+": ["5", 3] } // 8 (string "5" becomes number 5)
Truthiness
Boolean operators use configurable truthiness rules (see
Truthiness Evaluation). Under the default,
TruthyEvaluator::JavaScript:
- Falsy:
false,0,"",null,[],{} - Truthy: everything else
Custom Operators
You register your own operators on the engine builder. See Custom Operators for details.
let engine = Engine::builder()
.add_operator("myop", MyOperator)
.build();
Custom operators follow the same syntax in rules:
{ "myop": [arg1, arg2] }
A custom operator registered under the name of a built-in the engine has
never runs, because the built-in wins at compile time.
EngineBuilder::try_add_operator refuses such a name with a
ConfigurationError.
There is no preserve operator. To emit a JSON object verbatim from a
rule, use templating mode (Engine::builder().with_templating(true).build(),
Cargo feature templating). Literal scalars and arrays work inline in
every mode.
Emitting a key that is an operator name. Because a single-key object is an operator invocation,
{"type": {"var": "x"}}runs thetypeoperator rather than emitting atypefield, and the same applies to every name on this page plus any custom operator you register. In templating mode you can opt into an escape prefix,Engine::builder().with_template_key_escape('$'), and write{"$type": ...}to emittype({"$$type": ...}emits a literal$type). See Structured Objects.