0.0
The project is in a healthy, maintained state
Ruby implementation of JsonLogic. JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value. Ships with a compliance runner for the official test suite.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies
 Project Readme

json-logic-rb

Ruby implementation of JsonLogic — simple and extensible. Ships with a compliance runner for the official test suite.

build rubygems license

Table of Contents

  • What
  • Install
  • Quick start
  • How
    • 1) Operators (default)
    • 2) Lazy operators
  • Supported Built-in Operations
    • Accessing Data
    • Logic and Boolean Operations
    • Numeric Operations
    • Array Operations
    • String Operations
    • Miscellaneous
  • Extending (add your own operator)
  • Public Interface
  • Compliance & tests
  • Security
  • License
  • Contributing

What

JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value.

Install

gem install json-logic-rb

Quick start

require 'json_logic'

rule = { "+" => [1, 2, 3] }

JsonLogic.apply(rule)
# => 6.0

With data:

JsonLogic.apply({ "var" => "user.age" }, { "user" => { "age" => 42 } })
# => 42

How

There are two kinds of operators in this implementation. This mapping follows the official behavior described on jsonlogic.com.

1) Operators (default)

For operators, 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 operators that do not control evaluation order.

Official docs:

2) Lazy operators

Some operators must control whether and when their arguments are evaluated. They implement branching, short-circuiting, or “apply a rule per item” semantics. For these lazy operators, the engine passes raw sub-rules and current data. The operator then evaluates only the sub-rules it actually needs.

Groups and references:

How per-item evaluation works:

  1. The first argument is a rule that returns the list of items — evaluated once to a Ruby array.

  2. The second argument is the per-item rule — evaluated for each item with that item as the current root.

  3. For reduce, the current item is also available as "current", and the running total as "accumulator" (this mirrors the reference usage in docs and tests).

Examples

# filter: keep numbers >= 2
JsonLogic.apply(
  { "filter" => [ { "var" => "ints" }, { ">=" => [ { "var" => "" }, 2 ] } ] },
  { "ints" => [1,2,3] }
)
# => [2, 3]

# reduce: sum using "current" and "accumulator"
JsonLogic.apply(
  { "reduce" => [
      { "var" => "ints" },
      { "+" => [ { "var" => "accumulator" }, { "var" => "current" } ] }, 0 ]
  },
  { "ints" => [1,2,3,4] }
)
# => 10.0

Why laziness matters?

Lazy operators 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] }] })
# => 1

# "if" evaluates only the 'then' branch when condition is true.
# The 'else' branch with 1/0 is never evaluated.
JsonLogic.apply({ "if" => [true, 42, { "/" => [1, 0] }] })
# => 42

In this gem (library) / returns nil on divide-by-zero, but these examples show why lazy evaluation is required by the spec: branching and boolean operators must not evaluate unused branches.


Supported Built-in Operations

Below is a checklist that mirrors the sections on jsonlogic.com/operations.html and shows what this gem (library) implements.

Accessing Data

Operator Supported Notes
var
missing Returns array of missing keys; works with complex sources (e.g. merge result).
missing_some Returns [] when the minimum is met, otherwise missing keys.
Operator Supported Notes
if
==
=== Strict equality (same type and value).
!=
!==
! Follows JsonLogic truthiness.
!! Follows JsonLogic truthiness.
or Returns first truthy / last value;
and Returns first falsy / last value;
?: Returns the truth.
Operator / Topic Supported Notes
> >= < <=
Between (pattern) Use </<= with 3 args (not a separate op).
max / min
+ - * / Unary - negates; unary + casts to number; / returns nil on divide‑by‑zero.
%
Operator Supported Notes
map
reduce Per‑item rule sees "current" and "accumulator".
filter Keeps items where per‑item rule is truthy (follows JsonLogic truthiness).
all false on empty; all per‑item are truthy (follows JsonLogic truthiness).
none true on empty; none per‑item are truthy (follows JsonLogic truthiness).
some false on empty; any per‑item is truthy (follows JsonLogic truthiness).
merge Flattens arguments into a single array; non‑arrays cast to singleton arrays.
in If second arg is an array: membership test.
Operator Supported Notes
in If second arg is a string: substring test.
cat Concatenate arguments as strings (no delimiter).
substr Start index can be negative; length can be omitted or negative (mirrors the official behavior).

Miscellaneous

Operator Supported Notes
log 🚫 Not implemented by default (and not part of the compliance tests).

Summary: From the reference page’s list, everything except log is implemented. (“Between” is not a standalone operator, but the </<= 3‑argument form is supported.)


Extending (add your own operator)

Operation Type

Each operator is a class.

  • Value operations inherit JsonLogic::Operation (engine passes values).

  • Lazy operations inherit JsonLogic::LazyOperation (engine passes raw sub‑rules).

  • Enumerable operations inherit JsonLogic::EnumerableOperation (standardized data binding for per‑item rules).

Guide

First, create the Class for you Operation based on it's type:

class JsonLogic::Operations::StartsWith < JsonLogic::Operation
  def self.op_name = "starts_with"  # {"starts_with": [string, prefix]}

  def call((str, prefix), _data)
    str.to_s.start_with?(prefix.to_s)
  end
end

Second, register your operation:

JsonLogic::Engine.default.registry.register(JsonLogic::Operations::StartsWith)

Use it!

rule = {
  "if" => [
    { "starts_with" => [ { "var" => "email" }, "admin@" ] },
    "is_admin",
    "regular_user"
  ]
}

p JsonLogic.apply(rule, { "email" => "admin@example.com" })
# => "is_admin"
p JsonLogic.apply(rule, { "email" => "user@example.com" })
# => "regular_user"

Public Interface

Use the high-level facade to evaluate a JsonLogic rule against input and get a plain Ruby value back. If you need more control (e.g., custom operator sets or multiple engines), use JsonLogic::Engine

# Main facade
JsonLogic.apply(rule, data = nil)  # => value

# Engine/Registry (advanced)
engine = JsonLogic::Engine.default
engine.evaluate(rule, data)

# Register a custom op class (auto‑registration is also supported)
engine.registry.register(JsonLogic::Operations::StartsWith)

Compliance and tests

Optional: quick self-test

ruby test/selftest.rb

Official test suite

  1. Fetch the official suite
mkdir -p spec/tmp
curl -fsSL https://jsonlogic.com/tests.json -o spec/tmp/tests.json
  1. Run it
ruby script/compliance.rb spec/tmp/tests.json

Expected output

# => Compliance: X/X passed

Security

  • Rules are data, not code; no Ruby eval.
  • When evaluating untrusted rules, consider adding a timeout and error handling at the call site.
  • Custom operations should be pure (no IO, no network, no shell).

Authors