FAQ
Frequently asked questions about datalogic-rs.
General
What is JSONLogic?
JSONLogic is a way to write portable, safe logic rules as JSON. The specification is available at jsonlogic.com.
Why use datalogic-rs instead of the reference implementation?
- Performance — significantly faster than JS implementations
- Thread Safety —
LogicisSend + Sync; wrap inArcto share - Extended Operators — datetime, regex, error handling, more
- Type Safety — full Rust type system benefits
- WASM Support — same engine in browsers and Node.js
- Zero
unsafe— the crate is built with#![forbid(unsafe_code)]
Is datalogic-rs fully compatible with JSONLogic?
Yes. datalogic-rs passes the complete official JSONLogic test suite. It also includes additional operators that extend the specification.
Rust Usage
Should I use v4 or v5?
Use v5 for new projects. The API is cleaner, the default build does
not pull in serde_json, and the arena evaluation path is exposed
directly. See the Migration Guide for the move from v4.
v5 is a hard cliff — there is no compatibility shim, so plan a single
cutover when upgrading from v4. The repo-root MIGRATION.md has the
per-call cookbook.
How do I share compiled rules across threads?
Logic is Send + Sync. Wrap it in Arc to share:
#![allow(unused)]
fn main() {
use datalogic_rs::Engine;
use std::sync::Arc;
let engine = Arc::new(Engine::new());
let compiled = engine.compile_arc(rule).unwrap();
let compiled_clone = Arc::clone(&compiled);
std::thread::spawn(move || {
let mut session = engine.session();
session.eval_str(&compiled_clone, data)
});
}
Why are custom operator arguments pre-evaluated in v5?
The pre-evaluated, arena-based design makes custom operators behave like
built-ins: the engine recurses, hands you &DataValue<'a> borrows, and you
return another arena allocation. This avoids the boundary conversion that
the v4 Operator trait paid on every call and removes the need for a
separate Evaluator trait.
If you need lazy / short-circuit semantics like and / or, that lives in
built-in operators today (none of the v5 short-circuit operators are
exposed through the public custom-operator surface).
What’s the difference between eval, eval_str, eval_into, and evaluate?
| Method | Input | Output | Notes |
|---|---|---|---|
datalogic_rs::eval_str (and eval / eval_into) | R: IntoLogic, D: OwnedInput | String (or OwnedDataValue / T) | Module-level helper backed by a default engine. Use when you don’t need custom operators or non-default config. |
Engine::eval_str (and eval / eval_into) | R: IntoLogic, D: OwnedInput | String (or OwnedDataValue / T) | One-shot through a configured engine. Allocates a fresh arena internally. |
Engine::evaluate | &Logic, any EvalInput, &Bump | &'a DataValue<'a> | Hot path. Caller owns the arena, result borrows from it. |
Session::eval_str (and eval / eval_into) | &Logic, D: EvalInput | String (or OwnedDataValue / T) | Reuses the session’s arena across calls. Caller calls session.reset() between batches. |
Session::eval_borrowed | &Logic, D: EvalInput | &'a DataValue<'a> | Zero-copy result; valid until the next &mut self call. |
The typed eval_into::<T> paths (and the serde_json::Value boundary
on EvalInput / IntoLogic) require feature = "serde_json".
JavaScript / WASM Usage
Do I need to call init() in Node.js?
No. The Node.js target does not require initialization:
const { evaluate } = require('@goplasmatic/datalogic-wasm');
evaluate('{"==": [1, 1]}', '{}', false);
Why do I need to JSON.stringify my data?
The WASM interface uses string-based communication for maximum compatibility:
const result = evaluate(
JSON.stringify(logic),
JSON.stringify(data),
false
);
const value = JSON.parse(result);
How do I use this with TypeScript?
Types are included in the package:
import init, { evaluate, CompiledRule } from '@goplasmatic/datalogic-wasm';
await init();
const result: string = evaluate('{"==": [1, 1]}', '{}', false);
React UI
Why does the editor need explicit dimensions?
React Flow (the underlying library) requires a container with defined dimensions to calculate node positions and viewport.
<div style={{ height: '500px' }}>
<DataLogicEditor value={expression} />
</div>
Can I use this with Next.js?
Yes. For the App Router, wrap in a client component:
'use client';
import '@xyflow/react/dist/style.css';
import '@goplasmatic/datalogic-ui/styles.css';
import { DataLogicEditor } from '@goplasmatic/datalogic-ui';
export function Editor({ expression }) {
return <DataLogicEditor value={expression} />;
}
Operators
How do I access array elements by index?
Use the var operator with numeric path segments:
{"var": "items.0.name"}
What’s the difference between == and ===?
==: Loose equality (with type coercion, like JavaScript)===: Strict equality (no type coercion)
{"==": [1, "1"]} // true
{"===": [1, "1"]} // false
How do I handle missing data?
Use the missing or missing_some operators:
{"if": [
{"missing": ["user.email"]},
"Email required",
"Valid"
]}
Or use default values with var:
{"var": ["user.email", "no-email@example.com"]}
What happened to the preserve operator?
It was removed in v5. Literal scalars and arrays already pass through
inline, and templated objects belong in templating mode
(Engine::builder().with_templating(true).build(), requires
feature = "templating").
Configuration
How do I handle NaN in arithmetic?
Use the NanHandling configuration:
#![allow(unused)]
fn main() {
use datalogic_rs::{Engine, EvaluationConfig, NanHandling};
let config = EvaluationConfig {
arithmetic_nan_handling: NanHandling::IgnoreValue,
..Default::default()
};
let engine = Engine::builder().with_config(config).build();
}
Options: ThrowError (default), CoerceToZero, IgnoreValue, ReturnNull.
How do I change division by zero behavior?
#![allow(unused)]
fn main() {
use datalogic_rs::{EvaluationConfig, DivisionByZeroHandling};
let config = EvaluationConfig {
division_by_zero: DivisionByZeroHandling::ReturnNull,
..Default::default()
};
}
Options: ReturnSaturated (default — f64::MAX/MIN), ThrowError,
ReturnNull, ReturnInfinity.
Troubleshooting
“Invalid operator” error
In standard mode, unrecognized keys are treated as errors. Either:
- Fix the operator name (operators are case-sensitive)
- Register a custom operator on the builder
- Enable templating mode (
feature = "templating") —Engine::builder().with_templating(true).build()
Performance issues with large expressions
- Use
Sessionfor repeated calls (arena reuse) - Drop to
Engine::evaluatewith a caller-managedbumpalo::Bumpfor the absolute hot path - Profile with
feature = "trace"to identify slow sub-expressions
WASM initialization fails
Ensure you await init() before calling other functions:
await init();
const result = evaluate(...);
For more troubleshooting, see the Troubleshooting Guide.