Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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.

License

Notifications You must be signed in to change notification settings

tavrelkate/json-logic-rb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

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 types of operations in this implementation: Default Operations and Lazy Operations.

1. Default 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:

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 current data. The operator then evaluates only the sub-rules it actually needs.

Groups and references:

How enumerable 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".

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.0

Why laziness matters?

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

In this gem / 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 Operations (Built‑in)

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 🚫

Adding Operations

Need a custom operation? It’s straightforward.

Quick — register a Proc or Lambda

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] })
# => 42

Is useful for rapid prototyping with minimal boilerplate; Later you can “promote” it into a full class or use additional features.

1) Pick the Operation type

Choose one of:

  • Default
class JsonLogic::Operations::StartsWith < JsonLogic::Operation; end

For anonymous functions:

JsonLogic.add_operation("starts_with", lazy: false) do; end
  • Lazy
class JsonLogic::Operations::StartsWith < JsonLogic::LazyOperation; end

For anonymous functions:

JsonLogic.add_operation("starts_with", lazy: true) do; end

See §How for details.

2) Enable JsonLogic Semantics (optional)

Enable semantics to mirror JsonLogic’s comparison/truthiness in Ruby:

using JsonLogic::Semantics

See §JsonLogic Semantic for details.

3) Create an Operation and provide a machine name

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
end

4) Register the new operation

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

After registration, use it in rules:

{ "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" // true

Ruby:

1 >= "1.0"
# ArgumentError: comparison of Integer with String failed

Ruby (with JsonLogic semantics enabled):

using JsonLogic::Semantics

1 >= "1.0" # => true

Truthiness

JsonLogic’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:

!![]
# => true

While JsonLogic as was mentioned before has it's own truthiness:

In Ruby (with JsonLogic Semantic):

include JsonLogic::Semantics

truthy?([])
# => false

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.
  • Operations are pure (no IO, no network, no shell).
  • Rules have no write access to anything.

License

MIT — see LICENSE.

Authors

About

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.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Languages