/datalogic-rs

A fast, type-safe Rust implementation of JSONLogic for evaluating logical rules as JSON. Perfect for business rules engines and dynamic filtering in Rust applications.

Primary LanguageRustApache License 2.0Apache-2.0

Plasmatic Logo

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.

License: Apache 2.0 Rust Crates.io Documentation Downloads

🏢 Organization📖 Docs🐛 Report a Bug


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));

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.
  1. 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
  2. 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.