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/errorsbranch need templating mode. Compile the rule as a template: in Rust,Engine::compile_template(with thetemplatingCargo feature) or an engine built withwith_templating(true); in the bindings,compileTemplate(compile_templatein Python,CompileTemplatein Go and .NET) or thetemplatingengine 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
errorandvalidbranches 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/prioritybranches 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
- Open the interactive playground (live Studio) to edit any of these rules (switch on Templating for the templating recipes).
- Read Structured Objects (Templating) for full details on multi-key output templates and key escaping.
- Browse the full operators overview for everything these recipes are built from.
- See production service patterns in Integration: Express (Node.js) and Integration: Spring Boot (JVM).
- See how datalogic-rs compares to other JSONLogic engines.