Ruby implementation of JsonLogic — simple and extensible. Ships with a compliance runner for the official test suite.
- What
- Install
- Quick start
- How
- Supported Operations (Built‑in)
- Adding Operations
- JsonLogic Semantic
- Compliance and tests
- Security
- License
- Authors
JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value.
gem install json-logic-rbrequire 'json_logic'
rule = { "+" => [1, 2, 3] }
JsonLogic.apply(rule)
# => 6.0With data:
JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } })
# => 42There are two types of operations in this implementation: Default Operations and Lazy Operations.
For Default Operations, the engine 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.
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 current data. The operator then evaluates only the sub-rules it actually needs.
Groups and references:
-
Branching / boolean control —
if,?:,and,or,varLogic & boolean operations • Truthiness -
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.0Lazy operations prevent evaluation of branches you do not need. If division by zero raised an error (hypothetically), lazy control would avoid it:
# "or" short-circuits: 1 is truthy, so the right side is NOT evaluated.
# If the right side were evaluated eagerly, it would attempt 1/0 (error).
JsonLogic.apply({ "or" => [1, { "/" => [1, 0] }] })
# => 1In this gem
/returnsnilon divide‑by‑zero, but these examples show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches.
Below is a list that mirrors the sections on jsonlogic.com/operations.html and shows what this gem (library) implements. From the reference page’s list, everything except log is implemented.
| 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 |
🚫 |
Need a custom operation? It’s straightforward.
Register little anonymous functions, by passing a Proc or Lambda.
JsonLogic.add_operation("times2") { |(value), _| value.to_i * 2 }Once the function added, you can use it in your logic.
JsonLogic.apply({ "times2" => [21] })
# => 42Is useful for rapid prototyping with minimal boilerplate; Later you can “promote” it into a full class or use additional features.
Choose one of:
- Default
class JsonLogic::Operations::StartsWith < JsonLogic::Operation; endFor anonymous functions:
JsonLogic.add_operation("starts_with", lazy: false) do; end- Lazy
class JsonLogic::Operations::StartsWith < JsonLogic::LazyOperation; endFor anonymous functions:
JsonLogic.add_operation("starts_with", lazy: true) do; endSee §How for details.
Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby:
using JsonLogic::SemanticsSee §JsonLogic Semantic for details.
Operation methods use a consistent call shape.
- The first parameter is the array of operator arguments.
- The second is the current data.
Thanks to Ruby’s destructuring, you can unpack the argument array right in the method signature.
class JsonLogic::Operations::StartsWith < JsonLogic::Operation
def self.name = "starts_with"
def call((str, prefix), _data)
# str, prefix are ALREADY evaluated to Ruby values
str.to_s.start_with?(prefix.to_s)
end
endJsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith)After registration, use it in rules:
{ "starts_with": [ { "var": "email" }, "admin@" ] }All supported Operations follow JsonLogic semantics.
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" # => trueJsonLogic’s truthiness differs from Ruby’s (see https://jsonlogic.com/truthy.html).
In Ruby, only false and nil are falsey. In JsonLogic empty strings and empty arrays are also falsey.
In Ruby:
!![]
# => trueWhile JsonLogic as was mentioned before has it's own truthiness:
In Ruby (with JsonLogic Semantic):
include JsonLogic::Semantics
truthy?([])
# => falseOptional: 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 passed- Rules are data, not code; no Ruby eval.
- Operations are pure (no IO, no network, no shell).
- Rules have no write access to anything.
MIT — see LICENSE.