Skip to content

tavrelkate/json-logic-rb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

95 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jsonlogic core jsonlogic community compliance 100% Build Status rubygems license

json-logic-rb

Ruby implementation of JsonLogic. Pure and extensible. Full compliance with both core and community-extended specifications.

Table of Contents


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-rb

If needed – add to your Gemfile

gem "json-logic-rb"

Then install

bundle install

Quick start

require 'json_logic'

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

JsonLogic.apply(rule)
# => 6.0

With data:

require 'json_logic'

rule = { "var" => "user.age" }
data = { "user" => { "age" => 42 } }

JsonLogic.apply(rule, data)
# => 42

Compliance

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

Supported Operations (Built-in)

Operator Supported Source
Data / Presence
var jsonlogic
val jsonlogic community
missing jsonlogic
missing_some jsonlogic
exists jsonlogic community
Logic and Boolean Operations
if jsonlogic
?: jsonlogic
and jsonlogic
or jsonlogic
! jsonlogic
!! jsonlogic
Comparison Operations
== jsonlogic
=== jsonlogic
!= jsonlogic
!== jsonlogic
> jsonlogic
>= jsonlogic
< jsonlogic
<= jsonlogic
Numeric Operations
+ jsonlogic
- jsonlogic
* jsonlogic
/ jsonlogic
% jsonlogic
min jsonlogic
max jsonlogic
Array Operations
map jsonlogic
reduce jsonlogic
filter jsonlogic
all jsonlogic
none jsonlogic
some jsonlogic
merge jsonlogic
String Operations
in jsonlogic
cat jsonlogic
substr jsonlogic
Community Extensions
?? jsonlogic community
try jsonlogic community
throw jsonlogic community
preserve jsonlogic community
Docs-only / Not implemented
log 🚫 jsonlogic docs

Adding Operations

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 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 and 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)
end

Lazy 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)
end

See §Laziness 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)
end

Lazy 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@" ] })

Laziness

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:

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:

Example #1

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

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

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

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

!![]
# => true

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

In Ruby (with JsonLogic Semantic):

using JsonLogic::Semantics

!![]
# => false

Security

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

Maintainers

About

Ruby implementation of JsonLogic. JsonLogic rules are JSON trees. The engine walks that tree and returns a Ruby value. Full compliance with both core and community-extended specifications.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages