Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Array Operators

Operations for working with arrays, including iteration and transformation.

Feature flags (Rust crate). All array operators are baseline except sort, slice, group_by, and distinct, which require the ext-array feature. Every language binding enables all operator features. An engine built with EngineBuilder::with_families has those four only if it names Family::ExtArray. See the feature table.

The JSONLogic iterators (filter, map, reduce, all, some, none) reject extra arguments with an Invalid Arguments error; sort, group_by and distinct ignore them. Inside an iterator, {"var": ""} is the current element; Scope levels reach enclosing elements and the element’s index or key.

merge

Concatenate multiple arrays into one.

Syntax:

{ "merge": [array1, array2, ...] }

Arguments:

  • array1, array2, … - Arrays to concatenate; merge wraps scalars and objects as single elements

Returns: A single array: the arguments concatenated one level deep. Nested arrays inside an argument stay as elements (no deep flatten), and merge drops null arguments and null elements.

Examples:

// Merge two arrays
{ "merge": [[1, 2], [3, 4]] }
// Result: [1, 2, 3, 4]

// Merge multiple
{ "merge": [[1], [2], [3]] }
// Result: [1, 2, 3]

// Non-arrays are wrapped
{ "merge": [[1, 2], 3, [4, 5]] }
// Result: [1, 2, 3, 4, 5]

// With variables
{ "merge": [{ "var": "arr1" }, { "var": "arr2" }] }
// Data: { "arr1": [1, 2], "arr2": [3, 4] }
// Result: [1, 2, 3, 4]

// Only one level is flattened
{ "merge": [[1, [2, [3]]], [4]] }
// Result: [1, [2, [3]], 4]

// Nulls are dropped
{ "merge": [[1, null, 2], null] }
// Result: [1, 2]

Try it:

Notes:

  • Flattens exactly one level; use nested merge calls for deeper structures
  • null arguments and null elements are skipped; a scalar argument is wrapped ({ "merge": 1 } is [1])

filter

Filter array elements based on a condition.

Syntax:

{ "filter": [array, condition] }

Arguments:

  • array - Array to filter (an object is also accepted, see Notes)
  • condition - Condition applied to each element (use {"var": ""} for current element)

Returns: Array of elements where condition is truthy. For an object input, an object of the key/value pairs whose value passes.

Examples:

// Filter numbers greater than 2
{ "filter": [
    [1, 2, 3, 4, 5],
    { ">": [{ "var": "" }, 2] }
]}
// Result: [3, 4, 5]

// Filter even numbers
{ "filter": [
    [1, 2, 3, 4, 5, 6],
    { "==": [{ "%": [{ "var": "" }, 2] }, 0] }
]}
// Result: [2, 4, 6]

// Filter objects by property
{ "filter": [
    { "var": "users" },
    { "==": [{ "var": "active" }, true] }
]}
// Data: {
//   "users": [
//     { "name": "Alice", "active": true },
//     { "name": "Bob", "active": false },
//     { "name": "Carol", "active": true }
//   ]
// }
// Result: [{ "name": "Alice", "active": true }, { "name": "Carol", "active": true }]

// Filter with multiple conditions
{ "filter": [
    { "var": "products" },
    { "and": [
        { ">": [{ "var": "price" }, 10] },
        { "var": "inStock" }
    ]}
]}

Try it:

Notes:

  • filter does not modify the original array
  • A field an element lacks reads as null, so { "filter": [{ "var": "rows" }, { "===": [{ "var": "v" }, null] }] } keeps the elements without v as well as those whose v is null
  • filter filters an object input by its values and returns an object of the kept pairs: { "filter": [{ "var": "x" }, { ">": [{ "var": "" }, 3] }] } with { "x": { "a": 1, "b": 5 } } is { "b": 5 }
  • A null or missing input yields []; a scalar input is an Invalid Arguments error

map

Transform each element of an array.

Syntax:

{ "map": [array, transformation] }

Arguments:

  • array - Array to transform
  • transformation - Operation applied to each element

Returns: Array of transformed elements.

Examples:

// Double each number
{ "map": [
    [1, 2, 3],
    { "*": [{ "var": "" }, 2] }
]}
// Result: [2, 4, 6]

// Extract property from objects
{ "map": [
    { "var": "users" },
    { "var": "name" }
]}
// Data: {
//   "users": [
//     { "name": "Alice", "age": 30 },
//     { "name": "Bob", "age": 25 }
//   ]
// }
// Result: ["Alice", "Bob"]

// Create new objects
{ "map": [
    { "var": "items" },
    { "cat": ["Item: ", { "var": "name" }] }
]}
// Data: { "items": [{ "name": "A" }, { "name": "B" }] }
// Result: ["Item: A", "Item: B"]

// Square numbers
{ "map": [
    [1, 2, 3, 4],
    { "*": [{ "var": "" }, { "var": "" }] }
]}
// Result: [1, 4, 9, 16]

Try it:

Notes:

  • An object input maps over its values and returns an array ({ "a": 1, "b": 5 } doubled is [2, 10])
  • A null or missing input yields []; map treats a scalar input as a one-element array ({ "map": [5, { "*": [{ "var": "" }, 2] }] } is [10])

reduce

Reduce an array to a single value.

Syntax:

{ "reduce": [array, reducer, initial] }
{ "reduce": [array, reducer] }

Arguments:

  • array - Array to reduce
  • reducer - Operation combining accumulator and current element
  • initial - Initial value for accumulator (optional). When omitted, the first element seeds the accumulator and reduction starts from the second; an empty array without initial yields null

Returns: Final accumulated value.

Context Variables:

  • {"var": "current"} - Current element
  • {"var": "accumulator"} - Current accumulated value
  • Dotted paths read inside them: {"var": "current.price"}, {"var": "accumulator.total"}

Examples:

// Sum all numbers
{ "reduce": [
    [1, 2, 3, 4, 5],
    { "+": [{ "var": "accumulator" }, { "var": "current" }] },
    0
]}
// Result: 15

// Product of all numbers
{ "reduce": [
    [1, 2, 3, 4],
    { "*": [{ "var": "accumulator" }, { "var": "current" }] },
    1
]}
// Result: 24

// Concatenate strings
{ "reduce": [
    ["a", "b", "c"],
    { "cat": [{ "var": "accumulator" }, { "var": "current" }] },
    ""
]}
// Result: "abc"

// Find maximum
{ "reduce": [
    [3, 1, 4, 1, 5, 9],
    { "if": [
        { ">": [{ "var": "current" }, { "var": "accumulator" }] },
        { "var": "current" },
        { "var": "accumulator" }
    ]},
    0
]}
// Result: 9

// Count elements matching condition
{ "reduce": [
    [1, 2, 3, 4, 5, 6],
    { "+": [
        { "var": "accumulator" },
        { "if": [{ ">": [{ "var": "current" }, 3] }, 1, 0] }
    ]},
    0
]}
// Result: 3 (count of numbers > 3)

// Without initial: the first element seeds the accumulator
{ "reduce": [
    [1, 2, 3],
    { "+": [{ "var": "accumulator" }, { "var": "current" }] }
]}
// Result: 6

// Empty array without initial
{ "reduce": [
    [],
    { "+": [{ "var": "accumulator" }, { "var": "current" }] }
]}
// Result: null

Try it:

Notes:

  • An object input reduces over its values
  • A null, missing, or scalar input returns initial unchanged (null when initial is omitted)
  • The accumulator may nest at most 1,024 levels of arrays and objects. A reducer that wraps the accumulator once per element ([{ "var": "accumulator" }]) fails with an Invalid Arguments error once it passes that depth

all

Check if all elements satisfy a condition.

Syntax:

{ "all": [array, condition] }

Arguments:

  • array - Array to check
  • condition - Condition applied to each element

Returns: true if all elements satisfy condition, false otherwise.

Examples:

// All positive
{ "all": [
    [1, 2, 3],
    { ">": [{ "var": "" }, 0] }
]}
// Result: true

// All greater than 5
{ "all": [
    [1, 2, 3],
    { ">": [{ "var": "" }, 5] }
]}
// Result: false

// All users active
{ "all": [
    { "var": "users" },
    { "var": "active" }
]}
// Data: { "users": [{ "active": true }, { "active": true }] }
// Result: true

// Empty array returns false (in this engine, all-of-empty is false)
{ "all": [[], { ">": [{ "var": "" }, 0] }] }
// Result: false

Try it:

Notes (all, some, none):

  • Each stops at the first element that decides the result and does not evaluate the condition for the rest
  • An object input checks the object’s values
  • A null, missing, or scalar input counts as an empty collection: all is false, some is false, none is true

some

Check if any element satisfies a condition.

Syntax:

{ "some": [array, condition] }

Arguments:

  • array - Array to check
  • condition - Condition applied to each element

Returns: true if at least one element satisfies condition, false otherwise.

Examples:

// Any negative
{ "some": [
    [1, -2, 3],
    { "<": [{ "var": "" }, 0] }
]}
// Result: true

// Any greater than 10
{ "some": [
    [1, 2, 3],
    { ">": [{ "var": "" }, 10] }
]}
// Result: false

// Any admin user
{ "some": [
    { "var": "users" },
    { "==": [{ "var": "role" }, "admin"] }
]}
// Data: {
//   "users": [
//     { "role": "user" },
//     { "role": "admin" }
//   ]
// }
// Result: true

// Empty array returns false
{ "some": [[], { ">": [{ "var": "" }, 0] }] }
// Result: false

Try it:


none

Check if no elements satisfy a condition.

Syntax:

{ "none": [array, condition] }

Arguments:

  • array - Array to check
  • condition - Condition applied to each element

Returns: true if no elements satisfy condition, false otherwise.

Examples:

// None negative
{ "none": [
    [1, 2, 3],
    { "<": [{ "var": "" }, 0] }
]}
// Result: true

// None greater than 0
{ "none": [
    [1, 2, 3],
    { ">": [{ "var": "" }, 0] }
]}
// Result: false

// No banned users
{ "none": [
    { "var": "users" },
    { "var": "banned" }
]}
// Data: { "users": [{ "banned": false }, { "banned": false }] }
// Result: true

// Empty array returns true
{ "none": [[], { ">": [{ "var": "" }, 0] }] }
// Result: true

Try it:


sort

Sort an array.

Syntax:

{ "sort": [array] }
{ "sort": [array, ascending] }
{ "sort": [array, ascending, key_extractor] }

Arguments:

  • array - Array to sort (a value that resolves to an array)
  • ascending - Optional direction boolean: true (or omitted) sorts ascending, false sorts descending
  • key_extractor - Optional per-element expression that produces the sort key for each element

Returns: Sorted array.

Examples:

// Sort numbers (ascending by default)
{ "sort": [[3, 1, 4, 1, 5, 9]] }
// Result: [1, 1, 3, 4, 5, 9]

// Sort strings
{ "sort": [["banana", "apple", "cherry"]] }
// Result: ["apple", "banana", "cherry"]

// Sort descending
{ "sort": [{ "var": "nums" }, false] }
// Data: { "nums": [3, 1, 4, 1, 5, 9] }
// Result: [9, 5, 4, 3, 1, 1]

// Sort objects ascending by a key extractor
{ "sort": [
    { "var": "items" },
    true,
    { "var": "price" }
]}
// Data: {
//   "items": [
//     { "name": "B", "price": 20 },
//     { "name": "A", "price": 10 }
//   ]
// }
// Result: [{ "name": "A", "price": 10 }, { "name": "B", "price": 20 }]

Try it:

Notes:

  • The second argument is a direction boolean: true (or omitted) sorts ascending, false descending. A non-boolean direction falls back to ascending.
  • The optional third argument is a per-element key extractor, evaluated with each element as its context. sort has no a/b comparator form.
  • Numbers sort by exact value, so integers above 2^53 keep their order. Values of different types sort by type: null, booleans, numbers, strings, arrays, then objects. Two arrays, or two objects, tie.
  • The sort is stable: elements that tie keep their input order. A key field an element lacks reads as null, so that element ties with one whose field is null.
  • A null or missing input yields null (a literal null argument is Invalid Arguments). This differs from group_by and distinct, which yield [] for null input. An object or scalar input is Invalid Arguments.

slice

Extract a portion of an array or string.

Syntax:

{ "slice": [collection, start] }
{ "slice": [collection, start, end] }
{ "slice": [collection, start, end, step] }

Arguments:

  • collection - Source array, or a string (sliced by character)
  • start - Starting index (negative counts from end); null means the default (the first element)
  • end - Ending index, exclusive (optional, negative counts from end); null means the default (the end of the collection)
  • step - Optional stride (default 1); a negative step walks backwards, so [null, null, -1] reverses

Returns: Array slice, or a string slice for string input.

Examples:

// From index 2 to end
{ "slice": [[1, 2, 3, 4, 5], 2] }
// Result: [3, 4, 5]

// From index 1 to 3
{ "slice": [[1, 2, 3, 4, 5], 1, 3] }
// Result: [2, 3]

// Last 2 elements
{ "slice": [[1, 2, 3, 4, 5], -2] }
// Result: [4, 5]

// First 3 elements
{ "slice": [[1, 2, 3, 4, 5], 0, 3] }
// Result: [1, 2, 3]

// Pagination
{ "slice": [
    { "var": "items" },
    { "*": [{ "var": "page" }, 10] },
    { "+": [{ "*": [{ "var": "page" }, 10] }, 10] }
]}
// Data: { "items": [...], "page": 0 }
// Result: first 10 items

// Step: every second element
{ "slice": [[1, 2, 3, 4, 5], 0, 5, 2] }
// Result: [1, 3, 5]

// Negative step reverses
{ "slice": [[1, 2, 3, 4, 5], null, null, -1] }
// Result: [5, 4, 3, 2, 1]

// Strings are sliced by character
{ "slice": ["hello", 1, 3] }
// Result: "el"

{ "slice": ["hello", null, null, -1] }
// Result: "olleh"

Try it:

Notes:

  • A null or missing collection yields null, without evaluating the bounds
  • start, end and step must be integers or null. A whole float (2.0) counts as an integer; a fractional bound (1.5, written or computed), a string ("1") or a boolean is a NaN error
  • step of 0 is an Invalid Arguments error, as is a collection that is neither an array nor a string

group_by

Collapse an array into groups on a computed key.

Syntax:

{ "group_by": [array, key_expression] }

Arguments:

  • array - Array to group (a value that resolves to an array)
  • key_expression - Per-element expression that produces each element’s group key (evaluated with the element as its context, like sort’s key extractor)

Returns: An array of {"key": ..., "items": [...]} rows, which map, filter and sort take as input. Groups appear in order of first key occurrence, so the output is deterministic for a given input.

Examples:

// Group tasks by status
{ "group_by": [{ "var": "tasks" }, { "var": "status" }] }
// Data: { "tasks": [
//   { "id": 1, "status": "open" },
//   { "id": 2, "status": "done" },
//   { "id": 3, "status": "open" }
// ]}
// Result: [
//   { "key": "open", "items": [{ "id": 1, "status": "open" }, { "id": 3, "status": "open" }] },
//   { "key": "done", "items": [{ "id": 2, "status": "done" }] }
// ]

// Group meetings by calendar date in the user's timezone
{ "group_by": [
    { "var": "meetings" },
    { "format_date": [{ "var": "start_time" }, "dd MMM yyyy", "Asia/Kolkata"] }
]}
// Result: [ { "key": "17 Aug 2026", "items": [ ... ] }, ... ]

// Group numbers by a computed key
{ "group_by": [[1, 2, 3, 4, 5], { "%": [{ "var": "" }, 2] }] }
// Result: [
//   { "key": 1, "items": [1, 3, 5] },
//   { "key": 0, "items": [2, 4] }
// ]

Try it:

Notes:

  • group_by keeps keys as their evaluated values and does not stringify them: numbers, booleans, null, and objects group by deep equality.
  • Elements whose key expression misses (resolves to null) group together under a null key.
  • null or empty input yields []. Non-array input (scalar or object) is an error.

distinct

Drop duplicate elements, by value or by a computed key.

Syntax:

{ "distinct": [array] }
{ "distinct": [array, key_expression] }

Arguments:

  • array - Array to deduplicate (a value that resolves to an array)
  • key_expression - Optional per-element expression; when present, distinct deduplicates by the computed key instead of by value

Returns: Array with duplicates removed. The first occurrence wins, so output preserves input order.

Examples:

// Dedup by value
{ "distinct": [[3, 1, 3, 2, 1]] }
// Result: [3, 1, 2]

// Strict equality: 1 and "1" stay distinct
{ "distinct": [[1, "1", 1]] }
// Result: [1, "1"]

// Dedup objects structurally
{ "distinct": [{ "var": "items" }] }
// Data: { "items": [{ "a": 1 }, { "a": 2 }, { "a": 1 }] }
// Result: [{ "a": 1 }, { "a": 2 }]

// Dedup by key: one row per id, first revision wins
{ "distinct": [{ "var": "rows" }, { "var": "id" }] }
// Data: { "rows": [
//   { "id": 1, "rev": "a" },
//   { "id": 2, "rev": "b" },
//   { "id": 1, "rev": "c" }
// ]}
// Result: [{ "id": 1, "rev": "a" }, { "id": 2, "rev": "b" }]

Try it:

Notes:

  • Equality is strict deep equality (the same predicate in uses): mixed types never merge, arrays and objects compare structurally. An object counts as a datetime only in the single-key form { "datetime": "..." }, so records that share a datetime field but differ elsewhere stay distinct.
  • null or empty input yields []. Non-array input is an error.

See Also