Business rules as data. One engine, every runtime.
Write a JSONLogic rule once and evaluate it with the exact same engine in Rust, Node.js, the browser (WASM), Python, Go, Java, .NET, and PHP. One Rust core sits under every binding, so the eight runtimes cannot drift apart, and a rule evaluates in nanoseconds. Store rules as JSON and change pricing, eligibility, and flag logic in production without a redeploy.
Build, trace, and debug rules live in the playground.
- 🌐 One rule, every runtime: every binding runs the same compiled Rust core, so a rule evaluates with identical semantics on your backend, your edge workers, and your frontend. No cross-language drift, verified by a 2,128-case conformance battery in CI.
- 🔒 Sandboxed: evaluate user-submitted rules and formulas without arbitrary code execution. No
eval(), no scripting runtime, no I/O; the core forbids unsafe code. - ⚡ Nanosecond evaluation: rules compile to OpCode-dispatched programs that run in a reusable memory arena: 10.3 ns geomean, 7.0× the fastest JS engine, 83.6× the reference implementation.
- 🛠️ Ready-made rule builder: ship a visual editor and step-through debugger to your product dashboard with the companion React component, instead of building rule UI from scratch.
Rules are plain JSON, so you keep one copy of each rule, however many languages you run:
Rule: {"and": [{">=": [{"var": "age"}, 18]}, {"==": [{"var": "status"}, "active"]}]}
Data: {"age": 25, "status": "active"}
Result: trueThe same evaluation, one line in each runtime:
| Runtime | One-shot evaluation |
|---|---|
| Rust | datalogic_rs::eval_str(rule, data)? |
| Node.js | apply(rule, data) from @goplasmatic/datalogic-node |
| Browser / Edge (WASM) | new Engine().evalStr(rule, data) from @goplasmatic/datalogic-wasm |
| Python | apply(rule, data) from datalogic_py |
| Go | datalogic.Apply(rule, data) |
| Java / Kotlin | engine.apply(rule, data) |
| .NET (C#) | engine.Apply(rule, data) |
| PHP | $engine->apply($rule, $data) |
Every binding wraps the same core and passes the same 66-suite conformance battery, so the same input bytes produce the same output bytes. Each package README has the full quickstart for its language, and every binding ships the same three runnable programs under its examples/ folder, so the folders double as the parity demo.
| Language / Environment | Version | Package | Install | Guide |
|---|---|---|---|---|
| Rust | datalogic-rs |
cargo add datalogic-rs |
crate README | |
| Node.js (native prebuilds) | @goplasmatic/datalogic-node |
npm i @goplasmatic/datalogic-node |
node README | |
| Browser, Edge, Bun, Deno | @goplasmatic/datalogic-wasm |
npm i @goplasmatic/datalogic-wasm |
wasm README | |
| Python | datalogic-py |
pip install datalogic-py |
python README | |
| Go | datalogic-go |
go get github.com/GoPlasmatic/datalogic-rs/bindings/go/v5 |
go README | |
| Java / JVM (Kotlin, Scala) | io.github.goplasmatic:datalogic |
Maven / Gradle dependency | jvm README | |
| .NET (C#, F#) | Goplasmatic.Datalogic |
dotnet add package Goplasmatic.Datalogic |
dotnet README | |
| PHP | goplasmatic/datalogic |
composer require goplasmatic/datalogic |
php README | |
| C / FFI (embed anywhere) | n/a | datalogic-c |
GitHub release tarball, or cargo build --release |
c README |
| React visual editor | @goplasmatic/datalogic-ui |
npm i @goplasmatic/datalogic-ui |
ui README |
Encode pricing logic, fee schedules, eligibility and underwriting rules, transaction risk scoring, payment routing, access control, or form validation as JSON. Store rules in a database column, fetch them from an API, review them in a diff: logic changes ship without a deploy.
Rule: {"if": [
{">": [{"var": "cart.total"}, 100]}, "free-shipping",
{">": [{"var": "cart.total"}, 50]}, "flat-rate",
"standard"
]}
Data: {"cart": {"total": 127.5}}
Result: "free-shipping"Enable templating mode to pass JSON objects through to the output while evaluating embedded operators in place:
Template: {"greeting": {"cat": ["Hello ", {"var": "name"}]},
"isAdult": {">=": [{"var": "age"}, 18]}}
Data: {"name": "Jane", "age": 25}
Output: {"greeting": "Hello Jane", "isAdult": true}Templating is an engine option in every binding (Engine::builder().with_templating(true) in Rust, behind the templating Cargo feature). You can also choose the mode per compile (compile_template / compile_strict in Rust, compileTemplate / compileStrict in JavaScript), allowing a single engine to validate conditions strictly and compile output templates. An optional escape prefix (.with_template_key_escape('$') in Rust, available in every binding) lets you emit output keys that match operator names—for example, {"$type": ...} outputs the literal key "type" instead of running the type operator. See Structured Objects & Templating.
Let power users and admins write formulas without handing them a scripting engine:
Rule: {"+": [{"var": "subtotal"}, {"var": "tax"}, {"var": "shipping"}]}
Data: {"subtotal": 100, "tax": 8.5, "shipping": 5}
Result: 113.5Try any of these live in the playground, or browse the use-case cookbook for feature flags, fraud scoring, and data transformation recipes.
For admin portals and dashboards where non-engineers author rules, drop @goplasmatic/datalogic-ui into your React app. It bundles the WASM engine to compile and trace rules live, and it is the same component behind the online playground.
import { DataLogicEditor } from '@goplasmatic/datalogic-ui';
<DataLogicEditor
value={{ ">": [{ "var": "x" }, 10] }}
data={{ x: 42 }}
onChange={(newRule) => console.log('Rules modified:', newRule)}
/>Every binding exposes the same patterns, so what you learn in one language carries over to the rest of your stack:
| Pattern | Shape | Use when |
|---|---|---|
| One-shot | apply(rule, data) |
ad-hoc evaluation, scripts, low volume |
| Engine | construct with config / custom operators | non-default semantics, extensions |
| Compile once | engine.compile(rule) → evaluate many |
one rule, many payloads |
| Session | engine.session() |
hot loops; reuses the internal arena across evaluations |
| Parse once | DataHandle(json) → evaluate many rules against it |
one payload, many rules or repeat evaluations; skips the dominant per-call parse cost |
| Typed | session.evaluateBool/Int/Float/Truthy(rule, handle) |
predicates and scalar results; no JSON decode on the way out |
| Batch | session.evaluateBatch(rule, handles) / evaluateMany(rules, handle) |
many evaluations in one call, per-item errors that never fail the set |
| Check | engine.check(rule) / engine.compileChecked(rule) |
validating user-authored rules before you store or run them; each diagnostic carries a JSON Pointer into the rule |
| Inspect | rule.facts() / engine.operators() |
the data paths a rule reads and the operators it calls; the catalogue of built-ins for editors and allow-lists |
Every binding also has traced evaluation (evaluateWithTrace), which powers the visual debugger. Rust adds zero-copy evaluation into a caller-owned arena. See the Rust crate deep-dive for the full ladder.
Geomean execution time across 51 benchmark suites (Apple M2 Pro; median of 3 samples; ratios are pairwise shared-suite geomeans; methodology in tools/benchmark/BENCHMARK.md):
datalogic-rs (native Rust) | 10.3 ns (■) 1x
json-logic-engine (JS, compiled) | 63.3 ns (■■■■■■) 7.0x
json-logic-engine (JS, interpreted) | 234.8 ns (■■■■■■■■■■■■■■■■■■■■■■■) 25.8x
jsonlogic-rs (bestowinc Rust engine) | 264.2 ns (■■■■■■■■■■■■■■■■■■■■■■■■■■) 28.1x
json-logic-js (Reference JS library) | 465.1 ns (■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■) 83.6x
Rules compile to a simple AST with OpCode dispatch (no runtime string matching) and execute inside a reusable memory arena: single-digit nanoseconds for folded rules, 10-120 ns for context-dependent ones.
In Node.js, the native @goplasmatic/datalogic-node package is the fast path and runs close to native Rust. The WASM build trades speed for portability (900.5 ns geomean under Node, 88× native, but it runs anywhere JavaScript does). Use native on Node servers; use WASM in browsers, edge runtimes, Deno, and Bun.
Reproduce it yourself with cargo run --release -p datalogic-bench --bin compare. The full matrix and caveats are in tools/benchmark/BENCHMARK.md.
- Conformance, enforced in CI: passes the official JSONLogic suite plus an extended cross-binding battery: 2,128 cases across 66 suites, run against the same core every binding ships.
- 84 built-in operators: comparison, arithmetic, logic, strings, arrays, objects, datetime (with IANA timezones), error handling, tensor marshalling, and the flagd operators. You can also register custom operators written in each host language.
engine.operators()lists all active built-in operators from the same table that drives dispatch. - Checks before evaluation:
checkreports every static issue in a rule (unknown operators, invalid argument counts, or unknown timezones) along with an RFC 6901 JSON Pointer into the rule.factsreports the data paths and operators a compiled rule uses. - Operation budgets: bound untrusted rules with an operation budget (
ops_budget) and inspect exact per-run consumption (evaluate_metered_report/evaluateMetered). - Thread-safe evaluation: compiled
LogicisSend + Syncand can be shared across threads viaArc. - Zero
unsafe: the core engine forbids unsafe code (#![forbid(unsafe_code)]). - Zero-copy variables:
bumpalo-backed evaluation borrows read-through operations likevardirectly from the input. When a rule's data reads are statically known, input projection views only those paths from an owned orserde_jsoninput. - Serde-optional: the default Rust build has no
serde_jsondependency. Enable theserde_jsonfeature only when you want typed Rust interop. - Configurable semantics: division-by-zero behavior, NaN handling, truthiness rules, numeric coercions, operator-family allowlists, and missing-variable behavior (
missing_var: returnnullor raise an error) are all configurable on the engine. - Verifiable supply chain: npm packages publish from GitHub Actions with provenance attestation (verify with
npm audit signatures).
The opt-in flagd Cargo feature (enabled by default in every language binding) provides the fractional and sem_ver operators used by OpenFeature flagd flag definitions. fractional implements MurmurHash3 bucketing that is byte-compatible with the canonical Go evaluator, so users land in the exact same variant buckets across implementations. This lets datalogic-rs act as an in-process, flagd-compatible feature-flag evaluator across all eight runtimes.
v5 introduces a cleaner API: it renames DataLogic to Engine, CompiledLogic to Logic, and Operator to CustomOperator. One-shot evaluation now uses eval_str (returning a String) or eval_into::<T> (for typed values). The npm WASM package moved from @goplasmatic/datalogic to @goplasmatic/datalogic-wasm. See MIGRATION.md for the step-by-step v4 → v5 guide as well as notes on behavior changes and deprecations across 5.x releases.
- Documentation site (source in
docs/src/): operator reference, per-language guides, and advanced configuration - Online playground: build, trace, and debug rules in your browser
- How it compares: vs
json-logic-js,json-logic-engine,jsonlogic-rs, ZEN, and CEL - Rust API docs on docs.rs
- JSONLogic specification
- Architecture overview · Development guide · Contributing · Security policy · Migration guide · Changelog
- dataflow-rs (Plasmatic): workflow and rules automation engine where every route condition is a compiled
datalogic-rsrule. - datafake-rs (Plasmatic): mock JSON data generator configured with JSONLogic expressions.
If you run datalogic-rs in production, add your project—a one-line PR or issue is enough.
See CONTRIBUTING.md for contribution guidelines, DEVELOPMENT.md for environment setup and build commands, ARCHITECTURE.md for structural diagrams, and CODE_OF_CONDUCT.md for community standards. Bring questions and ideas to Discussions.
Created by Plasmatic, building open-source tools for financial infrastructure and data processing.
Licensed under Apache 2.0. See LICENSE for details.