Ruby implementation of JsonLogic. Pure and extensible. Full compliance with both core and community-extended specifications.
- What
- Install
- Quick start
- Compliance
- Supported Operations (Built-in)
- Adding Operations
- Laziness
- JsonLogic Semantic
- Security
- License
- Maintainers
JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value.
Download the gem locally
gem install json-logic-rbIf needed – add to your Gemfile
gem "json-logic-rb"Then install
bundle installrequire 'json_logic'
rule = { "+" => [1, 2, 3] }
JsonLogic.apply(rule)
# => 6.0With data:
require 'json_logic'
rule = { "var" => "user.age" }
data = { "user" => { "age" => 42 } }
JsonLogic.apply(rule, data)
# => 42The JsonLogic specification provides test suites — concrete inputs with expected outputs that validate the implementation of operations. The specification comes in two variants:
The "extra" exists because "core" hasn't changed in years — and that’s fine, "core" is a solid, finished foundation. Think of it as v1, while "extra" is the v2+ evolution as there are no visible plans to change the original.
| Operator | Supported | Source |
|---|---|---|
| Data / Presence | ||
var |
✅ | |
val |
✅ | |
missing |
✅ | |
missing_some |
✅ | |
exists |
✅ | |
| Logic and Boolean Operations | ||
if |
✅ | |
?: |
✅ | |
and |
✅ | |
or |
✅ | |
! |
✅ | |
!! |
✅ | |
| Comparison Operations | ||
== |
✅ | |
=== |
✅ | |
!= |
✅ | |
!== |
✅ | |
> |
✅ | |
>= |
✅ | |
< |
✅ | |
<= |
✅ | |
| Numeric Operations | ||
+ |
✅ | |
- |
✅ | |
* |
✅ | |
/ |
✅ | |
% |
✅ | |
min |
✅ | |
max |
✅ | |
| Array Operations | ||
map |
✅ | |
reduce |
✅ | |
filter |
✅ | |
all |
✅ | |
none |
✅ | |
some |
✅ | |
merge |
✅ | |
| String Operations | ||
in |
✅ | |
cat |
✅ | |
substr |
✅ | |
| Community Extensions | ||
?? |
✅ | |
try |
✅ | |
throw |
✅ | |
preserve |
✅ | |
| Docs-only / Not implemented | ||
log |
🚫 |
Don’t expect JsonLogic to include every specialized operation. It’s intentionally small and not a programming language. It will never do everything.
Before you reach for a custom solution, see if you can express your logic using the §Supported Operations (Built-in). Often, a simple change in perspective is all you need to get the job done with what's already there.
If that doesn't cut it, adding a custom operation is straightforward. Keep it simple: start with a Proc or a Lambda. If needed – promote it to a Class.
Enable semantics to mirror JsonLogic’s comparison and truthiness in Ruby.
See §JsonLogic Semantic for details.
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) }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 §Laziness for details.
Use immediately:
JsonLogic.apply({ "starts_with" => [ { "var" => "email" }, "admin@" ] })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@" ] })There are two types of operations: Default Operations and Lazy 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 transform like
merge.
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:
- Logic and Boolean Operations — short-circuit/branching like
or. - Comparison operations — equality/ordering like
==. - Array operations — enumerable evaluation like
map.
Example #1
# filter: keep numbers >= 2
JsonLogic.apply(
{ "filter" => [ { "var" => "ints" }, { ">=" => [ { "var" => "" }, 2 ] } ] },
{ "ints" => [1,2,3] }
)
# => [2, 3]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.
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 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
!![]
# => false- RULES ARE DATA; NO RUBY EVAL;
- OPERATIONS ARE PURE; NO IO, NO NETWORK; NO SHELL;
- RULES HAVE NO WRITE ACCESS TO ANYTHING;
MIT — see LICENSE.