JsonLogic Extensions
Semantius extends the standard JsonLogic spec with additional variables and operators tailored to record-level computed fields, validation rules, and row-level select rules. This page documents only the extensions; all standard operators (if, and, or, var, ==, +, map, filter, reduce, etc.) work as defined upstream.
Built-in variables
These variables are automatically available inside computed_fields and validation_rules expressions. Access them with the standard var operator, e.g. {"var": "$now"}.
| Variable | Description |
|---|---|
$today | The current server date (no time component). Useful for date comparisons such as “expires before today”. |
$now | The current server timestamp. Useful for stamping fields like submitted_at or comparing against a deadline. |
$user_id | The id of the currently authenticated user. null when the expression runs outside a user session (for example during a background job). |
$old | A snapshot of the record’s previous state on update. null on insert. Use it to compare new vs. old values, e.g. {"!=": [{"var": "status"}, {"var": "$old.status"}]}. |
Built-in variables are scoped to the record being evaluated. They are not written back to the record, they exist only for the duration of the expression.
Custom operators
Bind a local variable: let
Evaluates an expression once and exposes the result under a name for the body that follows.
Form: {"let": ["<name>", <value>, <body>]}
{
"let": [
"total",
{"+": [{"var": "subtotal"}, {"var": "tax"}]},
{">": [{"var": "total"}, 100]}
]
}
Use it to avoid recomputing the same sub-expression multiple times or to make complex rules more readable.
Load a related record by id: set_record
Fetches an entire record from another entity by its id and binds it to a name, then evaluates the body with that record available.
Form: {"set_record": ["<name>", "<entityName>", <idExpression>, <body>]}
{
"set_record": [
"customer",
"customers",
{"var": "customer_id"},
{"==": [{"var": "customer.status"}, "active"]}
]
}
If the referenced record does not exist, the bound name resolves to null. Use dotted paths in var to read fields of the loaded record (e.g. {"var": "customer.email"}).
Check the current user’s permission: has_permission
Returns true when the current user holds the named permission, false otherwise. Does not throw.
Form: {"has_permission": "<permission_name>"}
{"if": [
{"has_permission": "orders.approve"},
"approved",
"pending_review"
]}
Enforce a permission: require_permission
Returns true when the current user holds the named permission, and raises an authorization error otherwise. Use this in validation rules to forbid an action outright.
Form: {"require_permission": "<permission_name>"}
{"require_permission": "orders.delete"}
Detect a field change on update: value_changed
Returns true when the named field’s value differs from its previous value on update, and true on insert (treating a new record as “changed”). Returns false only when the field’s value is the same as before.
Form: {"value_changed": "<field_name>"}
{"if": [
{"value_changed": "status"},
{"throw_error": "Status changes require manager approval"},
true
]}
Concatenate strings with SQL semantics: concat
Concatenates all arguments into a single string. Unlike the standard cat operator, concat treats null arguments as empty strings (mirroring SQL CONCAT) and serializes non-string values (numbers, booleans, arrays, objects) to their JSON text form.
Form: {"concat": [<value1>, <value2>, ...]}
{"concat": ["Order #", {"var": "id"}, " for ", {"var": "customer_name"}]}
Choose concat when some inputs may be null and you want them to disappear rather than produce the string "null".
Test a string against a regular expression: is_match
Tests whether a string value matches a regular expression pattern. Returns true on a match, false otherwise. null values always return false.
Form: {"is_match": [<value>, "<pattern>"]}
{"is_match": [{"var": "email"}, "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"]}
Useful in validation rules for format checks (email, phone, postal code, SKU prefix) without needing a separate constraint mechanism.
Raise a validation error: throw_error
Aborts the current operation with the given error message. Typically used as a branch of an if expression to enforce a business rule with a custom message.
Form: {"throw_error": "<message>"}
{"if": [
{"<": [{"var": "quantity"}, 1]},
{"throw_error": "Quantity must be at least 1"},
true
]}
The message surfaces to the caller as a constraint-violation error.