Semantius Logo
Docs Business Logic JsonLogic Extensions

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"}.

VariableDescription
$todayThe current server date (no time component). Useful for date comparisons such as “expires before today”.
$nowThe current server timestamp. Useful for stamping fields like submitted_at or comparing against a deadline.
$user_idThe id of the currently authenticated user. null when the expression runs outside a user session (for example during a background job).
$oldA 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.


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.