datalogic-rs
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.
Key Features
- 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.
Getting Started
1. Basic Usage
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));
2. Quick JSON Evaluation
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));
3. Thread-Safe Evaluation
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(); });
Installation
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"
Examples
Conditional Logic
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"));
Array Operations
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]));
String Operations
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!"));
Math Operations
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));
Custom Operators
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));
Configuration
Customize evaluation behavior to match your needs:
Basic Configuration
use datalogic_rs::{DataLogic, EvaluationConfig, NanHandling}; use serde_json::json; // Configure how non-numeric values are handled in arithmetic let config = EvaluationConfig::default() .with_nan_handling(NanHandling::IgnoreValue); let engine = DataLogic::with_config(config); // Non-numeric values are ignored instead of throwing errors let logic = json!({"+": [1, "text", 2]}); let compiled = engine.compile(&logic).unwrap(); let result = engine.evaluate_owned(&compiled, json!({})).unwrap(); assert_eq!(result, json!(3)); // "text" is ignored
Configuration Options
| Option | Description | Default |
|---|---|---|
| NaN Handling | How to handle non-numeric values in arithmetic | ThrowError |
| Division by Zero | How to handle division by zero | ReturnBounds |
| Truthy Evaluator | How to evaluate truthiness (JavaScript, Python, StrictBoolean, Custom) | JavaScript |
| Loose Equality Errors | Whether to throw errors for incompatible types in == |
true |
| Numeric Coercion | Rules for converting values to numbers | Permissive |
Custom Truthiness
use datalogic_rs::{DataLogic, EvaluationConfig, TruthyEvaluator}; use std::sync::Arc; // Only positive numbers are truthy let custom_evaluator = Arc::new(|value: &serde_json::Value| -> bool { value.as_f64().map_or(false, |n| n > 0.0) }); let config = EvaluationConfig::default() .with_truthy_evaluator(TruthyEvaluator::Custom(custom_evaluator)); let engine = DataLogic::with_config(config);
Configuration Presets
// Safe arithmetic - ignores invalid values let engine = DataLogic::with_config(EvaluationConfig::safe_arithmetic()); // Strict mode - throws more errors let engine = DataLogic::with_config(EvaluationConfig::strict());
Combining Configuration with Structure Preservation
let config = EvaluationConfig::default() .with_nan_handling(NanHandling::CoerceToZero); let engine = DataLogic::with_config_and_structure(config, true);
Advanced Features
Nested Data Access
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"));
Error Handling
let logic = json!({ "try": [ { "/": [10, { "var": "divisor" }] }, 0 // Default value on error ] });
Async Support
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)); }
Use Cases
Feature Flags
{
"and": [
{ "==": [{ "var": "user.country" }, "US"] },
{ "or": [
{ "==": [{ "var": "user.role" }, "beta_tester"] },
{ ">=": [{ "var": "user.account_age_days" }, 30] }
] }
]
}Dynamic Pricing
{
"if": [
{ ">=": [{ "var": "cart.total" }, 100] },
{ "-": [{ "var": "cart.total" }, { "*": [{ "var": "cart.total" }, 0.1] }] },
{ "var": "cart.total" }
]
}Fraud Detection
{
"or": [
{ "and": [
{ "!=": [{ "var": "transaction.billing_country" }, { "var": "user.country" }] },
{ ">=": [{ "var": "transaction.amount" }, 1000] }
] },
{ ">=": [{ "var": "transaction.attempts_last_hour" }, 5] }
]
}Supported Operators
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 |
Architecture
- 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
CompiledLogicstructure 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
Performance Optimizations
- 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
About Plasmatic
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.
License
Licensed under Apache 2.0. See LICENSE for details.