Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Use Cases & Examples

JSONLogic recipes for common scenarios. Every rule on this page is plain JSON: you write it once, store it where you store data (a database row, a config file, an API payload), and evaluate it unchanged from any language datalogic-rs has a binding for. Each recipe gives the rule, a sample payload and the result; standard-mode recipes also embed a live widget that runs them on the page. A few recipes build output objects in templating mode, and a note under each one says so. Their results appear in the order the template writes its keys, which is the order the engine emits them for JSON text input. Node.js and Python sort an object’s keys when they convert a native object (rule, data or result), which gives the same JSON value.

Run any of these in your language

The pattern is the same in every language: compile the rule once, then evaluate it against as many payloads as you need.

use datalogic_rs::Engine;

let engine = Engine::new();
let rule = engine
    .compile(r#"{"==": [{"var": "user.plan"}, "premium"]}"#)
    .unwrap();

let mut session = engine.session();
for payload in payloads {
    println!("{}", session.eval_str(&rule, payload).unwrap());
    session.reset(); // reset between evaluations to keep memory flat
}
import { Engine } from '@goplasmatic/datalogic-node';

const engine = new Engine();
const rule = engine.compile({ '==': [{ var: 'user.plan' }, 'premium'] });

for (const payload of payloads) {
  console.log(rule.evaluate(payload));
}
from datalogic_py import Engine

engine = Engine()
rule = engine.compile({"==": [{"var": "user.plan"}, "premium"]})

for payload in payloads:
    print(rule.evaluate(payload))
import datalogic "github.com/GoPlasmatic/datalogic-rs/bindings/go/v5"

engine := datalogic.NewEngine()
defer engine.Close()

rule, _ := engine.Compile(`{"==": [{"var": "user.plan"}, "premium"]}`)
defer rule.Close()

for _, payload := range payloads {
    out, _ := rule.Evaluate(payload)
    fmt.Println(out)
}
import com.goplasmatic.datalogic.Engine;
import com.goplasmatic.datalogic.Rule;

try (Engine engine = new Engine();
     Rule rule = engine.compile("{\"==\": [{\"var\": \"user.plan\"}, \"premium\"]}")) {
    for (String payload : payloads) {
        System.out.println(rule.evaluate(payload));
    }
}
using Goplasmatic.Datalogic;

using var engine = new Engine();
using var rule = engine.Compile("""{"==": [{"var": "user.plan"}, "premium"]}""");

foreach (var payload in payloads)
{
    Console.WriteLine(rule.Evaluate(payload));
}
use Goplasmatic\Datalogic\Engine;

$engine = new Engine();
$rule = $engine->compile('{"==": [{"var": "user.plan"}, "premium"]}');

foreach ($payloads as $payload) {
    echo $rule->evaluate($payload), "\n";
}

A few operators sit behind Cargo features in the Rust crate (ext-string, datetime); recipes that use them say so. Every language binding ships with all operator features enabled, so outside Rust there is nothing to switch on.

Feature Flags

Control feature availability based on user attributes.

Basic Feature Flag

Feature available to premium users in the US:

{
    "and": [
        {"==": [{"var": "user.plan"}, "premium"]},
        {"==": [{"var": "user.country"}, "US"]}
    ]
}

Data:

{
    "user": {"plan": "premium", "country": "US"}
}

Result: true

Try it:

Percentage Rollout

Enable for 20% of users, bucketing on the numeric user ID modulo 100:

{
    "<": [
        { "%": [{ "var": "user.id" }, 100] },
        20
    ]
}

Data:

{"user": {"id": 12345}}

Result: false (12345 % 100 = 45, and 45 is not below the 20 cutoff)

For IDs that are strings, or buckets that must match OpenFeature flagd, use the fractional operator, which hashes the key with MurmurHash3.

Try it:

Beta Access

Enable for beta testers, employees, or users who signed up before a date. ends_with needs the ext-string feature in Rust; every binding has it.

{
    "or": [
        { "==": [{ "var": "user.role" }, "beta_tester"] },
        { "ends_with": [{ "var": "user.email" }, "@company.com"] },
        { "<": [{ "var": "user.signup_date" }, "2024-01-01"] }
    ]
}

Data:

{
    "user": {"role": "customer", "email": "sam@company.com", "signup_date": "2024-03-15"}
}

Result: true (the email marks this user as an employee)

Try it:


Dynamic Pricing

Compute a price from quantity, tiers or membership.

Discount by Quantity

20% off from 100 units, 10% off from 50 units, list price below that:

{
    "if": [
        { ">=": [{ "var": "quantity" }, 100] },
        { "*": [{ "var": "base_price" }, 0.8] },
        { "if": [
            { ">=": [{ "var": "quantity" }, 50] },
            { "*": [{ "var": "base_price" }, 0.9] },
            { "var": "base_price" }
        ]}
    ]
}

Data:

{"quantity": 75, "base_price": 100}

Result: 90 (10% discount)

Try it:

Tiered Pricing

The first 10 units cost $10, the next 40 cost $8, and every unit past 50 costs $6:

{
    "+": [
        { "*": [{ "min": [{ "var": "quantity" }, 10] }, 10] },
        { "*": [
            { "max": [{ "-": [{ "min": [{ "var": "quantity" }, 50] }, 10] }, 0] },
            8
        ]},
        { "*": [
            { "max": [{ "-": [{ "var": "quantity" }, 50] }, 0] },
            6
        ]}
    ]
}

Data:

{"quantity": 75}

Result: 570 (10 units at $10, 40 at $8, 25 at $6)

Try it:

Member Pricing

Members pay the product price minus their personal discount percentage:

{
    "if": [
        { "var": "user.is_member" },
        { "*": [
            { "var": "product.price" },
            { "-": [1, { "/": [{ "var": "user.member_discount" }, 100] }] }
        ]},
        { "var": "product.price" }
    ]
}

Data:

{
    "user": { "is_member": true, "member_discount": 15 },
    "product": { "price": 200 }
}

Result: 170 (15% member discount)

Try it:


Form Validation

Validate user input and report what failed.

Required Fields

Report which required fields are absent; the missing call inside the template returns that list:

{
    "if": [
        { "missing": ["name", "email", "password"] },
        {
            "valid": false,
            "errors": { "missing": ["name", "email", "password"] }
        },
        { "valid": true }
    ]
}

Data:

{"name": "Ada Lovelace"}

Result: {"valid": false, "errors": ["email", "password"]}

Templating recipe. Multi-key objects such as the valid / errors branch need templating mode. Compile the rule as a template: in Rust, Engine::compile_template (with the templating Cargo feature) or an engine built with with_templating(true); in the bindings, compileTemplate (compile_template in Python, CompileTemplate in Go and .NET) or the templating engine option. The inline widgets on this page run in standard mode, so paste this pair into the playground and switch on Templating to run it.

Field Constraints

Check email shape, password length and age range, and collect a message for each failed check. length needs the ext-string feature in Rust; every binding has it.

{
    "valid": { "and": [
        { "in": ["@", { "var": "email" }] },
        { ">=": [{ "length": { "var": "password" } }, 8] },
        { "and": [
            { ">=": [{ "var": "age" }, 18] },
            { "<=": [{ "var": "age" }, 120] }
        ]}
    ]},
    "errors": { "filter": [
        [
            { "if": [
                { "!": { "in": ["@", { "var": "email" }] } },
                "Invalid email format",
                null
            ]},
            { "if": [
                { "<": [{ "length": { "var": "password" } }, 8] },
                "Password must be at least 8 characters",
                null
            ]},
            { "if": [
                { "or": [
                    { "<": [{ "var": "age" }, 18] },
                    { ">": [{ "var": "age" }, 120] }
                ]},
                "Age must be between 18 and 120",
                null
            ]}
        ],
        { "!==": [{ "var": "" }, null] }
    ]}
}

Data:

{"email": "ada@example.com", "password": "short", "age": 25}

Result: {"valid": false, "errors": ["Password must be at least 8 characters"]}

Templating recipe. Compile it as a template (see Required Fields), or run it in the playground with Templating switched on.

Conditional Validation

If it is a business account, require a company name:

{
    "if": [
        { "and": [
            { "==": [{ "var": "account_type" }, "business"] },
            { "missing": ["company_name"] }
        ]},
        { "error": "Company name required for business accounts" },
        { "valid": true }
    ]
}

Data:

{"account_type": "business", "contact_email": "ops@acme.io"}

Result: {"error": "Company name required for business accounts"}

Templating recipe. The error and valid branches are output objects, so compile the rule as a template (see Required Fields), or run it in the playground with Templating switched on.


Access Control

Determine user permissions.

Role-Based Access

Admins can always act; editors only on resources they own:

{
    "or": [
        { "==": [{ "var": "user.role" }, "admin"] },
        { "and": [
            { "==": [{ "var": "user.role" }, "editor"] },
            { "==": [{ "var": "resource.owner_id" }, { "var": "user.id" }] }
        ]}
    ]
}

Data:

{
    "user": {"role": "editor", "id": 42},
    "resource": {"owner_id": 42}
}

Result: true

Try it:

Permission Checking

Check whether the required permission is in the user’s permission list:

{
    "in": [
        { "var": "required_permission" },
        { "var": "user.permissions" }
    ]
}

Data:

{
    "user": {
        "permissions": ["read", "write", "delete"]
    },
    "required_permission": "write"
}

Result: true

Try it:

Time-Based Access

Grant access only to permitted users, within allowed hours (9 AM to 6 PM), on a weekday:

{
    "and": [
        { "in": ["access_data", { "var": "user.permissions" }] },
        { "and": [
            { ">=": [{ "var": "current_hour" }, 9] },
            { "<": [{ "var": "current_hour" }, 18] }
        ]},
        { "in": [{ "var": "current_day" }, [1, 2, 3, 4, 5]] }
    ]
}

Data:

{
    "user": {"permissions": ["access_data", "export_reports"]},
    "current_hour": 14,
    "current_day": 3
}

Result: true

Try it:


Fraud Detection

Score and flag potentially fraudulent transactions.

Risk Scoring

Sum weighted signals: high amount (+30), new account (+25), billing/shipping country mismatch (+20), repeated attempts (+25), night-time hour, before 6 or from 22 (+15). A score above 50 flags the transaction for review:

{
    "+": [
        { "if": [{ ">": [{ "var": "amount" }, 1000] }, 30, 0] },
        { "if": [{ "<": [{ "var": "account_age_days" }, 7] }, 25, 0] },
        { "if": [
            { "!=": [{ "var": "billing_country" }, { "var": "shipping_country" }] },
            20,
            0
        ]},
        { "if": [{ ">": [{ "var": "attempts_last_hour" }, 3] }, 25, 0] },
        { "if": [
            { "or": [
                { "<": [{ "var": "hour" }, 6] },
                { ">=": [{ "var": "hour" }, 22] }
            ]},
            15,
            0
        ]}
    ]
}

Data:

{
    "amount": 1500,
    "account_age_days": 3,
    "billing_country": "US",
    "shipping_country": "CA",
    "attempts_last_hour": 1,
    "hour": 14
}

Result: 75 (high amount + new account + different country)

Try it:

Velocity Checks

Flag when any velocity signal crosses its threshold: too many transactions in a short window, too much total volume, or the same card used from too many IPs:

{
    "or": [
        { ">": [{ "var": "transactions_last_hour" }, 10] },
        { ">": [{ "var": "total_amount_last_hour" }, 5000] },
        { ">": [{ "var": "unique_ips_last_day" }, 3] }
    ]
}

Data:

{
    "transactions_last_hour": 14,
    "total_amount_last_hour": 1200,
    "unique_ips_last_day": 2
}

Result: true (more than 10 transactions in the last hour)

Try it:


Data Transformation

Transform and reshape data.

API Response Mapping

Reshape raw records into an API response: rename fields, derive a full name, normalize email case, and compute counts. lower and length need the ext-string feature in Rust; every binding has it.

{
    "users": {
        "map": [
            { "var": "raw_users" },
            {
                "id": { "var": "user_id" },
                "fullName": { "cat": [{ "var": "first_name" }, " ", { "var": "last_name" }] },
                "email": { "lower": { "var": "email" } },
                "isActive": { "==": [{ "var": "status" }, "active"] }
            }
        ]
    },
    "total": { "length": { "var": "raw_users" } },
    "activeCount": { "length": {
        "filter": [
            { "var": "raw_users" },
            { "==": [{ "var": "status" }, "active"] }
        ]
    }}
}

Data:

{
    "raw_users": [
        {"user_id": 101, "first_name": "Ada", "last_name": "Lovelace", "email": "Ada@Example.COM", "status": "active"},
        {"user_id": 102, "first_name": "Alan", "last_name": "Turing", "email": "Alan.Turing@Example.COM", "status": "inactive"}
    ]
}

Result: {"users": [{"id": 101, "fullName": "Ada Lovelace", "email": "ada@example.com", "isActive": true}, {"id": 102, "fullName": "Alan Turing", "email": "alan.turing@example.com", "isActive": false}], "total": 2, "activeCount": 1}

Templating recipe. Compile it as a template (see Required Fields), or run it in the playground with Templating switched on.

Report Generation

Build a report object with a computed title, a generation timestamp and summary stats. format_date and now need the datetime feature and length the ext-string feature in Rust; every binding has both.

{
    "report": {
        "title": { "cat": ["Sales Report - ", { "var": "period" }] },
        "generated": { "format_date": [{ "now": [] }, "%Y-%m-%d %H:%M"] },
        "summary": {
            "totalSales": { "reduce": [
                { "var": "transactions" },
                { "+": [{ "var": "accumulator" }, { "var": "current.amount" }] },
                0
            ]},
            "avgTransaction": { "/": [
                { "reduce": [
                    { "var": "transactions" },
                    { "+": [{ "var": "accumulator" }, { "var": "current.amount" }] },
                    0
                ]},
                { "length": { "var": "transactions" } }
            ]},
            "topCategory": { "var": "top_category" }
        }
    }
}

Data:

{
    "period": "Q2 2026",
    "top_category": "Electronics",
    "transactions": [
        {"amount": 1200, "category": "Electronics"},
        {"amount": 450, "category": "Home"},
        {"amount": 900, "category": "Electronics"}
    ]
}

Result: {"report": {"title": "Sales Report - Q2 2026", "generated": "2026-07-03 09:41", "summary": {"totalSales": 2550, "avgTransaction": 850, "topCategory": "Electronics"}}} (generated reflects the evaluation timestamp, so it varies run to run)

Templating recipe. Compile it as a template (see Required Fields), or run it in the playground with Templating switched on.


Notification Rules

Determine when and how to send notifications.

Alert Conditions

Route by severity: an error rate above 10 pages someone immediately, above 5 posts a Slack warning, above 1 lands in the email digest, and anything lower sends nothing:

{
    "if": [
        { ">": [{ "var": "error_rate" }, 10] },
        { "channel": "pager", "priority": "critical" },
        { "if": [
            { ">": [{ "var": "error_rate" }, 5] },
            { "channel": "slack", "priority": "warning" },
            { "if": [
                { ">": [{ "var": "error_rate" }, 1] },
                { "channel": "email", "priority": "info" },
                null
            ]}
        ]}
    ]
}

Data:

{"error_rate": 7.5}

Result: {"channel": "slack", "priority": "warning"}

Templating recipe. The channel / priority branches are output objects, so compile the rule as a template (see Required Fields), or run it in the playground with Templating switched on.

User Preferences

Send only if the user has notifications enabled, subscribes to this notification type, and is outside their quiet hours. Quiet hours can wrap past midnight (22 to 8), so the rule tests one range when the start comes first and two ranges otherwise:

{
    "and": [
        { "var": "user.notifications_enabled" },
        { "in": [
            { "var": "notification.type" },
            { "var": "user.enabled_types" }
        ]},
        { "!": { "if": [
            { "<=": [{ "var": "user.quiet_start" }, { "var": "user.quiet_end" }] },
            { "and": [
                { ">=": [{ "var": "current_hour" }, { "var": "user.quiet_start" }] },
                { "<": [{ "var": "current_hour" }, { "var": "user.quiet_end" }] }
            ]},
            { "or": [
                { ">=": [{ "var": "current_hour" }, { "var": "user.quiet_start" }] },
                { "<": [{ "var": "current_hour" }, { "var": "user.quiet_end" }] }
            ]}
        ]}}
    ]
}

Data:

{
    "user": {
        "notifications_enabled": true,
        "enabled_types": ["security", "billing"],
        "quiet_start": 22,
        "quiet_end": 8
    },
    "notification": {"type": "security"},
    "current_hour": 14
}

Result: true (14:00 is outside 22:00 to 08:00; at current_hour 23 or 3 the result is false)

Try it:


Where next