json-logic-rb
Ruby implementation of JsonLogic — simple and extensible. Ships with a compliance runner for the official test suite.
Table of Contents
- What
- Install
- Quick start
- How
- 1. Default Operations
- 2. Lazy Operations
- Supported Operations (Built‑in)
- Adding Operations
- JsonLogic Semantic
- Compliance and tests
- Security
- License
- Authors
What
JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value.
Install
Download the gem locally
gem install json-logic-rbIf needed – add to your Gemfile
gem "json-logic-rb"Then install
bundle installQuick start
require 'json_logic'
rule = { "+" => [1, 2, 3] }
JsonLogic.apply(rule)
# => 6.0With data:
JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } })
# => 42How
There are two types of operations: Default Operations and Lazy Operations
1. Default Operations
For Default Operations, the it evaluates all arguments first and then calls the operator with the resulting Ruby values. This matches the reference behavior for arithmetic, comparisons, string operations, and other pure operations that do not control evaluation order.
Groups and references:
- Numeric operations
- String operations
-
Array operations — simple transforms like
merge, membershipin.
2. Lazy Operations
Some operations must control whether and when their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these Lazy Operations, the engine passes raw sub-rules and data. The operator then evaluates only the sub-rules it actually needs.
Groups and references:
-
Branching / boolean control —
if,?:,and,or,varLogic & boolean operations -
Enumerable operators —
map,filter,reduce,all,none,someArray operations
How enumerable per-item evaluation works:
- The first argument is a rule that returns the list of items — evaluated once to a Ruby array.
- The second argument is the per-item rule — evaluated for each item with that item as the current root.
- For
reduce, the current item is also available as"current", and the running total as"accumulator".
Example #1
# filter: keep numbers >= 2
JsonLogic.apply(
{ "filter" => [ { "var" => "ints" }, { ">=" => [ { "var" => "" }, 2 ] } ] },
{ "ints" => [1,2,3] }
)
# => [2, 3]Example #2
# reduce: sum using "current" and "accumulator"
JsonLogic.apply(
{ "reduce" => [
{ "var" => "ints" },
{ "+" => [ { "var" => "accumulator" }, { "var" => "current" } ] }, 0 ]
},
{ "ints" => [1,2,3,4] }
)
# => 10.0Why laziness matters?
Lazy operations prevent evaluation of branches you do not need.
If hypothetically division by zero raises an error, lazy control would avoid it.
JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] })
# => 1In this gem division returns nil on divide‑by‑zero, but this example show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches.
Supported Operations (Built‑in)
Below is a list that mirrors the sections on Json Logic Website Opeations and shows what this gem implements.
| Operator | Supported |
|---|---|
var |
✅ |
missing |
✅ |
missing_some |
✅ |
| Logic and Boolean Operations | |
if |
✅ |
== |
✅ |
=== |
✅ |
!= |
✅ |
!== |
✅ |
! |
✅ |
!! |
✅ |
or |
✅ |
and |
✅ |
?: |
✅ |
| Numeric Operations | |
map |
✅ |
reduce |
✅ |
filter |
✅ |
all |
✅ |
none |
✅ |
some |
✅ |
merge |
✅ |
in |
✅ |
| Array Operations | |
map |
✅ |
reduce |
✅ |
filter |
✅ |
all |
✅ |
none |
✅ |
some |
✅ |
merge |
✅ |
in |
✅ |
| String Operations | |
in |
✅ |
cat |
✅ |
substr |
✅ |
| Miscellaneous | |
log |
🚫 |
Adding Operations
Need a custom Operation? It’s straightforward. Start small with a Proc or Lambda. If needed – promote it to a Class.
Enable JsonLogic Semantics (optional)
Enable semantics to mirror JsonLogic’s comparison and truthiness in Ruby.
See §JsonLogic Semantic for details.
Parameters
Operator function use a consistent call shape:
-
First parameter: array of operator arguments (you can destructure it).
-
Second parameter: current data.
->((string, prefix), data) { string.to_s.start_with?(prefix.to_s) }Proc / Lambda
Pick the Operation type.
Default Operation mode passes values.
JsonLogic.add_operation("starts_with") do |(string_value, prefix_value), _data|
string_value.to_s.start_with?(prefix_value.to_s)
endLazy Operation mode passes raw rules (you evaluate them):
JsonLogic.add_operation("starts_with", lazy: true) do |(string_rule, prefix_rule), data|
string_value = JsonLogic.apply(string_rule, data)
prefix_value = JsonLogic.apply(prefix_rule, data)
string_value.to_s.start_with?(prefix_value.to_s)
endSee §How for details.
Use immediately:
JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] })Class
Pick the Operation type. It has the same call shape.
Default Operation – Inherit JsonLogic::Operation.
class JsonLogic::Operations::StartsWith < JsonLogic::Operation
def self.name = "starts_with"
def call(string_value, prefix_value), _data) = string_value.to_s.start_with?(prefix_value.to_s)
endLazy Operation – Inherit JsonLogic::LazyOperation.
Register explicitly:
JsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith)Now, Class is ready to use.
JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] })JsonLogic Semantic
All supported Operations follow JsonLogic semantics.
Comparisons
As JsonLogic primary developed in JavaScript it inherits JavaScript's type coercion in build-in Operations. JsonLogic (JS‑style) comparisons coerce types; Ruby does not.
JavaScript:
1 >= "1.0" // trueRuby:
1 >= "1.0"
# ArgumentError: comparison of Integer with String failedRuby (with JsonLogic semantics enabled):
using JsonLogic::Semantics
1 >= "1.0"
# => trueTruthiness
JsonLogic’s truthiness differs from Ruby’s (see Json Logic Website Truthy and Falsy).
In Ruby, only false and nil are falsey. In JsonLogic empty strings and empty arrays are falsey too.
In Ruby:
!![]
# => trueWhile JsonLogic as was mentioned before has it's own truthiness.
In Ruby (with JsonLogic Semantic):
using JsonLogic::Semantics
!![]
# => falseCompliance and tests
Optional: quick self-test
ruby test/selftest.rbOfficial test suite
- Fetch the official suite
mkdir -p spec/tmp
curl -fsSL https://jsonlogic.com/tests.json -o spec/tmp/tests.json- Run it
ruby script/compliance.rb spec/tmp/tests.jsonExpected output
# => Compliance: X/X passedSecurity
- RULES ARE DATA; NO RUBY EVAL;
- OPERATIONS ARE PURE; NO IO, NO NETWORK; NO SHELL;
- RULES HAVE NO WRITE ACCESS TO ANYTHING;
License
MIT — see LICENSE.