A fast, production-ready Rust engine for JSONLogic.
Effortlessly evaluate complex rules and dynamic expressions with a powerful, memory-efficient, and developer-friendly toolkit.
datalogic-rs
brings the power of JSONLogic to Rust, focusing on speed, safety, and ease of use. Whether you’re building feature flags, dynamic pricing, or complex validation, this engine is designed to be flexible and robust.
What’s New in v4? We’ve redesigned the API to be more ergonomic and developer-friendly. If you need maximum speed with arena allocation, v3 is still available and maintained. Choose v4 for a smoother experience, or stick with v3 for raw performance—both are supported.
- Thread-Safe: Compile your logic once, then evaluate it anywhere—no locks, no fuss.
- Intuitive API: Works seamlessly with
serde_json::Value
. - Fully Compliant: Passes the official JSONLogic test suite.
- Extensible: Add your own operators with a simple trait.
- Templating Support: Preserve object structures for dynamic output.
- Battle-Tested: Used in production, with thorough test coverage.
- Feature-Rich: Over 50 built-in operators, including datetime and regex.
- Async-Ready: Integrates smoothly with Tokio and async runtimes.
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let logic = json!({ ">": [{ "var": "age" }, 18] });
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate_owned(&compiled, json!({ "age": 21 })).unwrap();
assert_eq!(result, json!(true));
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let result = engine.evaluate_json(
r#"{ "+": [1, 2] }"#,
r#"{}"#
).unwrap();
assert_eq!(result, json!(3));
use datalogic_rs::DataLogic;
use serde_json::json;
use std::sync::Arc;
let engine = Arc::new(DataLogic::new());
let logic = json!({ "*": [{ "var": "x" }, 2] });
let compiled = engine.compile(&logic).unwrap();
// Share across threads
let engine2 = Arc::clone(&engine);
let compiled2 = Arc::clone(&compiled);
std::thread::spawn(move || {
engine2.evaluate_owned(&compiled2, json!({ "x": 5 })).unwrap();
});
Add this to your Cargo.toml
:
[dependencies]
datalogic-rs = "4.0"
# Or use v3 if you need arena-based allocation for maximum performance
# datalogic-rs = "3.0"
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let logic = json!({
"if": [
{ ">=": [{ "var": "age" }, 18] },
"adult",
"minor"
]
});
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate_owned(&compiled, json!({ "age": 25 })).unwrap();
assert_eq!(result, json!("adult"));
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let logic = json!({
"filter": [
[1, 2, 3, 4, 5],
{ ">": [{ "var": "" }, 2] }
]
});
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate_owned(&compiled, json!({})).unwrap();
assert_eq!(result, json!([3, 4, 5]));
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let logic = json!({ "cat": ["Hello, ", { "var": "name" }, "!"] });
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate_owned(&compiled, json!({ "name": "World" })).unwrap();
assert_eq!(result, json!("Hello, World!"));
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let logic = json!({ "max": [{ "var": "scores" }] });
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate_owned(&compiled, json!({ "scores": [10, 20, 15] })).unwrap();
assert_eq!(result, json!(20));
Extend the engine with your own logic:
use datalogic_rs::{DataLogic, Operator, ContextStack, Evaluator, Result, Error};
use serde_json::{json, Value};
struct DoubleOperator;
impl Operator for DoubleOperator {
fn evaluate(
&self,
args: &[Value],
_context: &mut ContextStack,
_evaluator: &dyn Evaluator,
) -> Result<Value> {
match args.first().and_then(|v| v.as_f64()) {
Some(n) => Ok(json!(n * 2.0)),
None => Err(Error::InvalidArguments("Expected number".to_string()))
}
}
}
let mut engine = DataLogic::new();
engine.add_operator("double".to_string(), Box::new(DoubleOperator));
let result = engine.evaluate_json(r#"{ "double": 21 }"#, r#"{}"#).unwrap();
assert_eq!(result, json!(42.0));
use datalogic_rs::DataLogic;
use serde_json::json;
let engine = DataLogic::new();
let logic = json!({ "var": "user.address.city" });
let data = json!({
"user": {
"address": {
"city": "New York"
}
}
});
let compiled = engine.compile(&logic).unwrap();
let result = engine.evaluate_owned(&compiled, data).unwrap();
assert_eq!(result, json!("New York"));
let logic = json!({
"try": [
{ "/": [10, { "var": "divisor" }] },
0 // Default value on error
]
});
Works seamlessly with async runtimes:
use datalogic_rs::DataLogic;
use serde_json::json;
use std::sync::Arc;
#[tokio::main]
async fn main() {
let engine = Arc::new(DataLogic::new());
let logic = json!({ "*": [{ "var": "x" }, 2] });
let compiled = engine.compile(&logic).unwrap();
let handle = tokio::task::spawn_blocking(move || {
engine.evaluate_owned(&compiled, json!({ "x": 5 }))
});
let result = handle.await.unwrap().unwrap();
assert_eq!(result, json!(10));
}
{
"and": [
{ "==": [{ "var": "user.country" }, "US"] },
{ "or": [
{ "==": [{ "var": "user.role" }, "beta_tester"] },
{ ">=": [{ "var": "user.account_age_days" }, 30] }
] }
]
}
{
"if": [
{ ">=": [{ "var": "cart.total" }, 100] },
{ "-": [{ "var": "cart.total" }, { "*": [{ "var": "cart.total" }, 0.1] }] },
{ "var": "cart.total" }
]
}
{
"or": [
{ "and": [
{ "!=": [{ "var": "transaction.billing_country" }, { "var": "user.country" }] },
{ ">=": [{ "var": "transaction.amount" }, 1000] }
] },
{ ">=": [{ "var": "transaction.attempts_last_hour" }, 5] }
]
}
Over 50 built-in operators, including:
Category | Operators |
---|---|
Comparison | == , === , != , !== , > , >= , < , <= |
Logic | and , or , ! , !! |
Arithmetic | + , - , * , / , % , min , max , abs , ceil , floor |
Control Flow | if , ?: (ternary), ?? (coalesce) |
Arrays | map , filter , reduce , all , some , none , merge , in , length , slice , sort |
Strings | cat , substr , starts_with , ends_with , upper , lower , trim , split |
Data Access | var , val , exists , missing , missing_some |
DateTime | datetime , timestamp , now , parse_date , format_date , date_diff |
Type | type (returns type name as string) |
Error Handling | throw , try |
Special | preserve (for structured object preservation) |
Custom | User-defined operators via Operator trait |
- Compilation: Parses and optimizes logic for fast evaluation.
- Evaluation: Uses OpCode dispatch and context stack for speed.
- Thread-Safe: Share compiled logic with zero-copy via Arc.
-
Compilation Phase: JSON logic is parsed and compiled into a
CompiledLogic
structure with:- Static evaluation of constant expressions
- OpCode assignment for built-in operators
- Thread-safe Arc wrapping for sharing across threads
-
Evaluation Phase: The compiled logic is evaluated against data with:
- Direct OpCode dispatch (avoiding string lookups)
- Context stack for nested evaluations
- Zero-copy operations where possible
- OpCode dispatch for built-in operators
- Static evaluation of constant expressions
- SmallVec for small arrays
- Arc sharing for thread safety
- Cow types for efficient value passing
Created by Plasmatic, building open-source tools for financial infrastructure and data processing.
Check out our other projects:
- DataFlow-rs: Event-driven workflow orchestration in Rust.
Licensed under Apache 2.0. See LICENSE for details.