JSONLogic Ruby Shiny JSON Logic

JSON Logic Documentation

Complete reference for all supported operations with interactive examples

# What is JSON Logic?

JSON Logic is a format for encoding business rules as JSON objects. It provides a portable, language-agnostic way to define logic that can be evaluated consistently across different platforms—whether in Ruby on the server, JavaScript in the browser, or any other language with a JSON Logic implementation.

Structure

A JSON Logic rule is a JSON object with a single key (the operator) and a value (the arguments):

{ "operator": [argument1, argument2, ...] }

Rules can be nested—any argument can itself be another rule, allowing you to build complex expressions from simple primitives.

Data

Rules are evaluated against a data object. Use the val operator to access values from this data:

Shiny JSON Logic

shiny_json_logic is a Ruby gem that implements the JSON Logic specification with up-to-date operators. It's designed to be safe, simple and easy to use.

usage.rb
require 'shiny_json_logic'

rule = { ">" => [{ "val" => "age" }, 18] }
data = { "age" => 25 }

ShinyJsonLogic.apply(rule, data)  # => true

# Aliases for easier migration from other gems
JsonLogic.apply(rule, data)   # => true
JSONLogic.apply(rule, data)   # => true

Why Shiny JSON Logic?

There are other JSON Logic implementations in Ruby, but they have limitations:

Feature shiny_json_logic json_logic json-logic-rb json_logic_ruby
Compatibility 99.7% 63.9% 67.7% 42.3%
Last updated 2026 2020 2025 2024
Ruby 2.x
Ruby 3.x
Ruby 4.x ? ?
val operator
try / throw
?? (nullish coalescing)
exists operator
Scope navigation in iterators
Actively maintained
Real-world use cases
JSON Logic is commonly used for feature flags, access control, form validation, dynamic pricing, eligibility rules, and any scenario where business logic needs to be configured without code deployments.

The rule checks if age > 18. Try changing the age value.

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Truthy and Falsy Values

JSON Logic uses specific rules to determine if a value is considered "truthy" (treated as true) or "falsy" (treated as false). These rules are important for logical operators like and, or, if, and !.

Falsy Values

  • false — the boolean false
  • null — null/nil value
  • 0 — the number zero
  • "" — empty string
  • [] — empty array
  • {} — empty object

Truthy Values

  • true — the boolean true
  • Non-zero numbers (1, -5)
  • Non-empty strings ("hello")
  • Non-empty arrays ([1, 2])
  • Non-empty objects ({"a": 1})
Watch out for "0" and "false"
The strings "0" and "false" are truthy because they are non-empty strings. Only the empty string "" is falsy.
Practical usage

Truthiness is used by logical operators:

  • {"and": [a, b]} — returns first falsy value, or last value
  • {"or": [a, b]} — returns first truthy value, or last value
  • {"if": [cond, then, else]} — evaluates then if cond is truthy
  • {"!": val} — returns true if val is falsy

Use !! to see if a value is truthy or falsy

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Root & Stacked Contexts

When you evaluate a JSON Logic rule, you provide a data object. This object becomes the root context—the starting point for all variable access.

Root Context

At the top level, val and var access values directly from your data object.

Stacked Contexts (Inside Iterators)

When you use iterators like map, filter, or reduce, a new context is pushed onto the stack. The current item becomes the new context, and the previous context moves "up" one level.

Example: mapping over users

context_stack.txt
Data: { "users": [{"name": "Alice"}, {"name": "Bob"}], "greeting": "Hello" }

Inside map, the context stack looks like:
┌───────────────────────────────────────┐
│ Level 0 (current): {"name": "Alice"}  │  ← {"val": "name"} or {"val": []}
├───────────────────────────────────────┤
│ Level 1: {index: 0}                   │  ← {"val": [[1], "index"]}
├───────────────────────────────────────┤
│ Level 2 (root): {users, greeting}     │  ← {"val": [[2], "greeting"]}
└───────────────────────────────────────┘
Scope navigation syntax

Use {"val": [[N], "key"]} to go up N levels in the context stack:

  • {"val": [[1], "index"]} — current iteration index
  • {"val": [[2], "key"]} — access root data (in a simple map)
  • {"val": [[3], "key"]} — go up 3 levels (nested iterators)
Why stacked contexts?
Stacked contexts let you access both the current item and outer data in the same expression. This is essential for operations like "prefix each name with a greeting from root data".

See how context changes inside iterators

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Operations

JSON Logic provides a rich set of operations organized into categories:

Each operation follows the same pattern: {"operator": arguments}. Arguments can be literal values or nested operations.

# Variable Access

Operators for retrieving values from the data object.

# var - Variable Access

Deprecation Notice
The var operator will be deprecated in favor of val. The dot notation used by var cannot handle keys that contain literal dots (e.g., "user.name" as a single key). We recommend using val for new rules.

Retrieves a value from the data object using dot notation for nested access. This is the most fundamental operation in JSON Logic - it's how you access your data.

Basic Usage

Access a top-level key from your data:

Dot Notation for Nested Access

Use dots to traverse nested objects. The path user.profile.email will access data["user"]["profile"]["email"].

Deep Nesting
You can go as deep as you need: {"var": "a.b.c.d.e"} works perfectly. If any part of the path doesn't exist, you get null.

Default Values

Provide a fallback value as the second element in an array. If the key doesn't exist or is null, the default is returned instead.

Missing vs Null
The default is used both when the key is missing AND when it exists but is null. If you need to distinguish between these cases, use the exists operator.

Empty String - Get Entire Data

Passing an empty string "" or null returns the entire data object. This is especially useful inside iterators.

Inside Iterators
When you're inside a map, filter, or reduce, the "current item" becomes the data context. Use {"var": ""} to access the current item itself, or {"var": "fieldName"} to access a field of the current item.

Try different examples to see how var works in various scenarios

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# val - Value Access

The recommended way to access values from the data object. Uses an array of keys for nested access and supports scope navigation in iterators.

Basic Usage

Access values using a single key or an array of keys for nested access:

Array Notation for Nested Access

Use an array of keys to traverse nested objects. Each element in the array is a key to traverse, so ["user", "profile", "email"] accesses data["user"]["profile"]["email"].

Scope Navigation

Inside iterators like map or filter, val can navigate up the scope stack using the special syntax [[N], "key"] where N is the number of levels to go up.

Scope Levels
[[1], "index"] accesses the current iteration's index. [[2], "key"] goes up to the parent scope (root data in a simple map). Each nested iterator adds another scope level.

Empty Array - Get Current Scope

Passing an empty array [] returns the current scope's data.

Try different examples to see how val works

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Logical Operations

Boolean logic operators.

Chaining support
All logical operators support chaining—you can pass any number of arguments: {"and": [a, b, c, d]} evaluates all values in order and returns the first falsy value (or the last value if all are truthy).

# and - Logical AND

Returns the first falsy value, or the last value if all are truthy. Uses short-circuit evaluation—stops as soon as it finds a falsy value.

Returns actual values
Unlike some languages, and returns the actual value that determined the result, not just true or false. This makes it useful for providing default values.

Explore how and works with different values

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# or - Logical OR

Returns the first truthy value, or the last value if all are falsy. Uses short-circuit evaluation—stops as soon as it finds a truthy value.

Returns actual values
Unlike some languages, or returns the actual value that determined the result, not just true or false. This makes it useful for providing default values.

Explore how or works with different values

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# ?? - Nullish Coalescing

Returns the first non-null value from a list of arguments. Similar to or, but only checks for null instead of falsy values.

?? vs or

Use ?? when you want to preserve falsy values like 0, false, or "":

  • {"or": [0, 10]}10 (0 is falsy)
  • {"??": [0, 10]}0 (0 is not null)
Like JavaScript's ??
This operator behaves like JavaScript's nullish coalescing operator (??). It's useful when 0, false, or "" are valid values you want to keep.

Find the first non-null value

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# ! - Logical NOT

Negates the truthiness of a value. Returns true if the value is falsy, false if it's truthy.

Negate values to flip their truthiness

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# !! - Double NOT (Boolean Cast)

Converts any value to its boolean equivalent. Always returns exactly true or false.

Truthiness rules
Falsy values: false, 0, "", null, [], {}. Everything else is truthy.

Convert values to their boolean equivalent

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Control Flow

Operators for conditional logic and branching.

Lazy evaluation
All control flow operators use lazy evaluation—only the branches that are needed get evaluated. This means you can safely use throw or expensive operations in branches that might not execute.

# if - Conditional

Conditional branching with if/then/else logic. Supports multiple conditions (elseif) and uses lazy evaluation—only the needed branch is evaluated.

Elseif pattern
Chain multiple conditions: {"if": [cond1, val1, cond2, val2, ..., default]}. The last odd argument is the default (else) value.

Conditional logic with if/then/else

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# ?: - Ternary Conditional

A shorthand alias for if with exactly three arguments. Mimics the ternary operator (condition ? then : else) found in many languages.

Same as if
{"?:": [cond, then, else]} is exactly equivalent to {"if": [cond, then, else]}. Use whichever reads better for your use case.

Ternary conditional operator

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Comparison Operations

Operators for comparing values.

Chaining comparisons

All comparison operators support chaining. When you pass more than 2 arguments, each adjacent pair is compared and all must be true:

{"<": [1, x, 10]}   // 1 < x AND x < 10
{">": [10, x, 1]}   // 10 > x AND x > 1
{"==": [a, b, c]}   // a == b AND b == c

This is especially useful for range checks: use < for exclusive bounds or <= for inclusive bounds.

# == - Loose Equality

Compares two values with type coercion. Strings and numbers are compared numerically when possible.

Compare values with type coercion

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# === - Strict Equality

Compares two values without type coercion. Both value and type must match.

Compare values without type coercion

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# != - Loose Inequality

Returns true if values are not equal (with type coercion).

Check if values are not equal

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# !== - Strict Inequality

Returns true if values are not strictly equal (no type coercion).

Check if values are not strictly equal

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# > - Greater Than

Tests if the first value is greater than the second.

Compare values with greater than

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# >= - Greater Than or Equal

Tests if the first value is greater than or equal to the second.

Compare values with greater than or equal

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# < - Less Than

Tests if the first value is less than the second.

Compare values with less than

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# <= - Less Than or Equal

Tests if the first value is less than or equal to the second.

Compare values with less than or equal

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Arithmetic Operations

Mathematical operators for numeric calculations.

Type coercion
All arithmetic operators coerce their arguments to numbers. Strings like "5" become 5, and null becomes 0.

# + - Addition

Adds numbers together. With a single argument, converts the value to a number. An empty array returns 0 (additive identity).

Add numbers together

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# - - Subtraction

Subtracts numbers. With a single argument, negates the value.

Subtract numbers

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# * - Multiplication

Multiplies numbers together. An empty array returns 1 (multiplicative identity).

Multiply numbers

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# / - Division

Divides numbers.

Reciprocal
With a single argument, division returns its reciprocal value (1/x).
Division by zero
Dividing by zero raises a NotANumber error.

Divide numbers

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# % - Modulo

Returns the remainder after division. The sign of the result follows the dividend (first operand), matching Ruby's remainder behavior.

Get remainder of division

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# min - Minimum

Returns the smallest value from a set of numbers.

Strict numeric
Unlike other arithmetic operators, min does not coerce types. All values must be numeric—strings, null, or booleans will raise an InvalidArguments error. Empty arrays also raise an error.

Find the minimum value

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# max - Maximum

Returns the largest value from a set of numbers.

Strict numeric
Unlike other arithmetic operators, max does not coerce types. All values must be numeric—strings, null, or booleans will raise an InvalidArguments error. Empty arrays also raise an error.

Find the maximum value

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# String Operations

Operators for working with text strings.

Automatic string conversion
All string operators automatically convert their arguments to strings. Numbers become their string representation, null becomes "null", and booleans become "true" or "false".

# cat - Concatenation

Concatenates values into a single string.

Concatenate strings

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# substr - Substring

Extracts a portion of a string. Takes the source string, a start index, and an optional length.

Negative indices

A negative start counts from the end of the string: {"substr": ["hello", -3]} returns "llo" (last 3 characters).

A negative length means "stop N characters from the end": {"substr": ["hello", 1, -1]} returns "ell" (from index 1, stopping 1 character before the end).

Extract substrings

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# in - Inclusion

Checks if a value exists within a string (as a substring) or within an array (as an element). The first argument is the needle, the second is the haystack.

Check for inclusion

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Array Operations

Operators for working with arrays: iterating, filtering, and transforming collections.

Iteration context

Inside iterators (map, filter, reduce, all, some, none), the current element becomes the data context. Use {"val": []} to access the current element, or {"val": "field"} to access a field of the current element.

To access the iteration index or parent scope, use val with scope navigation: {"val": [[1], "index"]} for the current index.

# merge - Merge Arrays

Flattens and merges multiple arrays into one. Non-array values are wrapped as single-element arrays.

Single level flatten
Merge only flattens one level deep. Nested arrays inside your arrays are preserved.

Merge arrays together

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# map - Map Over Array

Applies a transformation to each element in an array and returns a new array with the results.

Transform array elements

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# filter - Filter Array

Returns a new array containing only the elements that satisfy the given condition.

Filter array elements

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# reduce - Reduce Array

Reduces an array to a single value by applying an accumulator function to each element. Takes three arguments: the array, the reducer expression, and the initial accumulator value.

Special variables
Inside reduce, use {"val": "current"} for the current element and {"val": "accumulator"} for the running accumulated value.

Reduce array to single value

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# all - All Match

Returns true if all elements in the array satisfy the condition. Short-circuits on the first falsy result.

Empty arrays
Returns false for empty arrays.

Check if all elements match

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# some - Some Match

Returns true if at least one element in the array satisfies the condition. Short-circuits on the first truthy result.

Empty arrays
Returns false for empty arrays.

Check if some elements match

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# none - None Match

Returns true if no elements in the array satisfy the condition. The logical opposite of some.

Empty arrays
Returns true for empty arrays (vacuously true).

Check if no elements match

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Data Validation

Operators for validating data presence and structure.

Deprecation Notice
The missing and missing_some operators will be deprecated in favor of the exists operator, which provides a cleaner and more flexible way to check for key existence. We recommend using exists for new rules.

# exists - Check Key Existence

Checks if a path exists in the data object. Unlike var or val, this operator distinguishes between a missing key and a key that exists with a null value.

Basic Usage

Pass an array of keys representing the path to check:

Recommended operator
Use exists instead of missing or missing_some for new rules. It's more flexible and can be combined with logical operators for complex validation.

Check if keys exist in the data

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# missing - Check Missing Keys

Deprecation Notice
This operator will be deprecated in favor of exists. Consider using {"!": {"exists": ["key"]}} instead.

Returns an array of keys that are missing from the data object. Useful for validating that required fields are present before processing.

Nested key access
Use dot notation to check for nested keys: "user.profile.email" checks if data["user"]["profile"]["email"] exists.
Validation pattern
Combine with if to create validation rules: {"if": [{"missing": ["required_field"]}, "Field is required", "OK"]}

Check which keys are missing from the data

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# missing_some - Check Some Missing Keys

Deprecation Notice
This operator will be deprecated in favor of exists. Consider combining exists with logical operators for similar functionality.

Returns the missing keys only if fewer than the required minimum are present. Useful when you need "at least N of these fields" validation.

Syntax

{"missing_some": [minimum_required, ["key1", "key2", "key3", ...]]}
Return value logic
  • If enough keys are present (>= minimum), returns [] (empty array)
  • If not enough keys are present, returns the array of missing keys
Common use cases
  • Contact info: Require at least one of email, phone, or address
  • Authentication: Require either password or OAuth token
  • Shipping: Require at least 2 of street, city, zip, country

Check if minimum required fields are present

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# Error Handling

Operators for throwing and catching errors within rules.

Error context
When an error is caught by try, the error payload becomes the current scope in the handler. Use {"val": "type"} to access the error type, or other fields if the thrown value was an object.

# try - Try/Catch

Attempts to evaluate expressions in order, catching any errors and trying the next alternative. Returns the first successful result.

Error payload as scope
In the catch handler, the thrown value becomes the current scope. If you threw {"type": "X", "message": "Y"}, you can access both with {"val": "type"} and {"val": "message"}.

Catch errors and provide fallbacks

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result

# throw - Throw Error

Raises an error that stops evaluation and can be caught by try. The thrown value can be a string (used as the error type) or an object with custom fields.

Error type
When throwing a string, it becomes the error's type. When throwing an object, the type field is used if present.
Uncaught errors
If a throw is not wrapped in a try, the error will propagate up and cause the entire rule evaluation to fail.

Handling Errors in Ruby

When using shiny_json_logic in Ruby, uncaught errors raise ShinyJsonLogic::Errors::Base. You can rescue these errors and access the error type via the type attribute.

error_handling.rb
require 'shiny_json_logic'

rule = { "throw" => "ValidationError" }
data = {}

begin
  result = ShinyJsonLogic.apply(rule, data)
rescue ShinyJsonLogic::Errors::Base => e
  puts e.type  # => "ValidationError"
end
Error handling pattern
Use the type attribute to distinguish between different error types and handle them appropriately in your application logic.

Throw errors for exceptional conditions

Rule logic
Data input
Ruby shiny_json_logic
Hit run to check the result
JavaScript json-logic-engine
Hit run to check the result