JSON Expressions

Contents

Introduction

JSON-structured expressions provide an opportunity to filter and query API resources through composable operations.

Expressions are defined by the user through a simple domain-specific language. An expression will be evaluated with an API resource type as its input and may cross-reference properties within the given resource to produce a result.

Expression Format

Expressions are structured as a JSON object/tree describing operators and their operands, with any level of nesting. The structure resembles the composition of function calls in a typical programming language.

An operation consists of a single-property JSON object with an operator name as the property-key and a scalar operand or array of operands as the property value:

{ <operator> : <operand(s)> } => evaluated

Examples:

{ "toLower": "EXAMPLE" } => "example"

// Equivalent:
//   toLower("EXAMPLE") => "example"
{ "equal": [1, 1] } => true

// Equivalent:
//   equal(1, 1) => true

Operations may include sub-operations within their operand(s). Each operand for an operation will be evaluated recursively before the operation itself is evaluated. Any JSON objects contained within an operand will be evaluated as nested operations.

Examples:

{ "toUpper": {"toLower": "EXAMPLE"} } => "EXAMPLE"

// Equivalent:
//   toUpper(toLower("EXAMPLE")) => "EXAMPLE"
{ "equal": ["example", {"toLower": "EXAMPLE"}] } => true

// Equivalent:
//   equal("example", toLower("EXAMPLE")) => true
{ "equal": [{"toUpper": "example"}, {"toUpper": "example"}] } => true

// Equivalent:
//   equal(toUpper("example"), toUpper("example")) => true

Ambiguity arises when a JSON object-literal or value containing object-literals is desired as an operand, without it being interpreted as a nested operation. For this purpose, a special value operator is provided which returns its operand(s) completely unevaluated.

Examples:

{ "value": {"example": 123} } => {"example": 123}

// Equivalent:
//   value({"example": 123}) => {"example": 123}
{ "equal": [{"example": 123}, {"example": 123}] } => // Invalid operator name: example at path equal[0]

// Equivalent:
//   equal(example(123), example(123))
{ "equal": [{"value": {"example": 123}}, {"value": {"example": 123}}] } => true

// Equivalent:
//   equal({"example": 123}, {"example": 123}) => true

back to top

Supported Operators

Logical Operators

not

The not operator returns true if its operand evaluates to false. The operand must evaluate to a boolean.

Examples:

{ "not": true } => false
{ "not": {"equal": [2, 2]} } => false

back to top

and

The and operator returns true if all of the values within the given array of operands evaluate to true. All operands must evaluate to booleans.

Examples:

{ "and": [true, true] } => true
{ "and": [true, true, false] } => false
{ "and": [true, {"equal": [2, 2]}] } => true

back to top

or

The or operator returns true if any of the values within the given array of operands evaluate to true. All operands must evaluate to booleans.

Examples:

{ "or": [true, false] } => true
{ "or": [false, false, true] } => true
{ "or": [false, {"equal": [2, 2]}] } => true

back to top

Relational Operators

equal

The equal operator returns true if all of the values within the given array of operands are equal according to a deep-equality check. All operands must evaluate to equivalent or compatible types.

Examples:

{ "equal": [2, 2] } => true
{ "equal": [2, 3, 4] } => false
{ "equal": ["example", {"toLower": "EXAMPLE"}] } => true

back to top

notEqual

The notEqual operator returns true if not all of the values within the given array of operands are equal according to a deep-equality check. All operands must evaluate to equivalent or compatible types.

Examples:

{ "notEqual": [1, 2] } => true
{ "notEqual": [1, 1, 1, 2] } => true
{ "notEqual": ["EXAMPLE", {"toLower": "EXAMPLE"}] } => true

back to top

lessThan

The lessThan operator returns true if each of the values within the given array is less than its subsequent value. If 2 operands are provided, and both evaluate to dates, the operands will be compared as dates; otherwise, all operands must evaluate to numeric types.

The before operator is an alias for the lessThan operator.

Examples:

{ "lessThan": [1, 2] } => true
{ "lessThan": [1, 2, 3] } => true
{ "lessThan": ["2018-12-01T00:00:00.000Z", "2018-12-02T00:00:00.000Z"] } => true

back to top

lessThanEqual

The lessThanEqual operator returns true if each of the values within the given array is less than or equal to its subsequent value. If 2 operands are provided, and both evaluate to dates, the operands will be compared as dates; otherwise, all operands must evaluate to numeric types.

Examples:

{ "lessThanEqual": [1, 2] } => true
{ "lessThanEqual": [1, 2, 2] } => true
{ "lessThanEqual": ["2018-12-01T00:00:00.000Z", "2018-12-02T00:00:00.000Z"] } => true

back to top

greaterThan

The greaterThan operator returns true if each of the values within the given array is greater than its subsequent value. If 2 operands are provided, and both evaluate to date types, the operands will be compared as dates; otherwise, all operands must evaluate to numeric types.

The after operator is an alias for the greaterThan operator.

Examples:

{ "greaterThan": [2, 1] } => true
{ "greaterThan": [3, 2, 1] } => true
{ "greaterThan": ["2018-12-02T00:00:00.000Z", "2018-12-01T00:00:00.000Z"] } => true

back to top

greaterThanEqual

The greaterThanEqual operator returns true if each of the values within the given array is greater than or equal to its subsequent value. If 2 operands are provided, and both evaluate to date types, the operands will be compared as dates; otherwise, all operands must evaluate to numeric types.

Examples:

{ "greaterThanEqual": [2, 1] } => true
{ "greaterThanEqual": [2, 2, 1] } => true
{ "greaterThanEqual": ["2018-12-02T00:00:00.000Z", "2018-12-01T00:00:00.000Z"] } => true

back to top

String Operators

toLower

The toLower operator returns its operand to lower-case. The operand must evaluate to a string.

Examples:

{ "toLower": "EXAMPLE" } => "example"
{ "toLower": {"toUpper": "EXAMPLE"} } => "example"

back to top

toUpper

The toUpper operator returns its operand to upper-case. The operand must evaluate to a string.

Examples:

{ "toUpper": "example" } => "EXAMPLE"
{ "toUpper": {"toLower": "EXAMPLE"} } => "EXAMPLE"

back to top

startsWith

The startsWith operator returns true if its first operand starts with its second operand. Both operands must evaluate to strings.

Examples:

{ "startsWith": ["example", "ex"] } => true
{ "startsWith": [{"toLower": "EXAMPLE"}, "ex"] } => true

back to top

endsWith

The endsWith operator returns true if its first operand ends with its second operand. Both operands must evaluate to strings.

Examples:

{ "endsWith": ["example", "ample"] } => true
{ "endsWith": [{"toLower": "EXAMPLE"}, "ample"] } => true

back to top

contains (also see Collection Operators)

The contains operator returns true if its first operand contains its second operand. If the first operand evaluates to an array, the array will be searched for the second operand. If the first operand evaluates to a string, the string will be searched for the second operand which must also evaluate to a string.

Examples:

{ "contains": ["example", "amp"] } => true
{ "contains": [{"toLower": "EXAMPLE"}, "amp"] } => true

back to top

split

The split operator returns an array of strings, formed by splitting its first operand on its second operand. Both operands must evaluate to strings.

Examples:

{ "split": ["x-y-z", "-"] } => ["x", "y", "z"]
{ "split": [{"toLower": "X-Y-Z"}, "-"] } => ["x", "y", "z"]

back to top

format

The format operator returns a formatted string. The first operand must evaluate to a format string and the second operand must evaluate to an array of parameters for the format string.

Example:

{ "format": ["{0}-{1}", ["abc", 123]] } => "abc-123"

back to top

regexMatches

The regexMatches operator returns true if its second operand matches the pattern given by its first operand. Both operands must evaluate to strings, and the regex pattern must be ECMAScript-compatible.

Example:

{ "regexMatches": ["[a-z]{3}-[0-9]{3}", "abc-123"] } => true

back to top

parseJson

The parseJson operator returns its operand parsed into a JSON value. The operand must evaluate to a string.

Example:

{ "parseJson": "[1,2,3]" } => [1, 2, 3]

back to top

Date Operators

date

The date operator converts its operand to a date object. The operand must evaluate to a string.

Example:

{ "date": "2018-12-01T00:00:00.000Z" } => // Date("2018-12-01T00:00:00.000Z")

back to top

before

The before operator returns true if its first operand is before its second operand. Both operands must evaluate to date types. The date operator will need to be used to convert each operand, if dates are not automatically converted.

The before operator is an alias for the lessThan operator.

Examples:

{ "before": ["2018-12-01T00:00:00.000Z", "2018-12-02T00:00:00.000Z"] } => true
{ "before": [{"date": "2018-12-01T00:00:00.000Z"}, {"date": "2018-12-02T00:00:00.000Z"}] } => true

back to top

after

The after operator returns true if its first operand is after its second operand. Both operands must evaluate to date types. The date operator will need to be used to convert each operand, if dates are not automatically converted.

The after operator is an alias for the greaterThan operator.

Examples:

{ "after": ["2018-12-02T00:00:00.000Z", "2018-12-01T00:00:00.000Z"] } => true
{ "after": [{"date": "2018-12-02T00:00:00.000Z"}, {"date": "2018-12-01T00:00:00.000Z"}] } => true

back to top

addSeconds

The addSeconds operator returns a date-time value computed by adding its second operand as a number of seconds to its first operand. The first operand must evaluate to a date type. The date operator will need to be used to convert the first operand, if dates are not automatically converted.

Examples:

{ "addSeconds": ["2018-12-01T00:00:00.000Z", 1] } => // Date("2018-12-01T00:00:01.000Z")
{ "addSeconds": [{"date": "2018-12-01T00:00:00.000Z"}, 1] } => // Date("2018-12-01T00:00:01.000Z")

back to top

addMinutes

The addMinutes operator returns a date-time value computed by adding its second operand as a number of minutes to its first operand. The first operand must evaluate to a date type. The date operator will need to be used to convert the first operand, if dates are not automatically converted.

Examples:

{ "addMinutes": ["2018-12-01T00:00:00.000Z", 1] } => // Date("2018-12-01T00:01:00.000Z")
{ "addMinutes": [{"date": "2018-12-01T00:00:00.000Z"}, 1] } => // Date("2018-12-01T00:01:00.000Z")

back to top

addHours

The addHours operator returns a date-time value computed by adding its second operand as a number of hours to its first operand. The first operand must evaluate to a date type. The date operator will need to be used to convert the first operand, if dates are not automatically converted.

Examples:

{ "addHours": ["2018-12-01T00:00:00.000Z", 1] } => // Date("2018-12-01T01:00:00.000Z")
{ "addHours": [{"date": "2018-12-01T00:00:00.000Z"}, 1] } => // Date("2018-12-01T01:00:00.000Z")

back to top

addDays

The addDays operator returns a date-time value computed by adding its second operand as a number of days to its first operand. The first operand must evaluate to a date type. The date operator will need to be used to convert the first operand, if dates are not automatically converted.

Examples:

{ "addDays": ["2018-12-01T00:00:00.000Z", 1] } => // Date("2018-12-02T00:00:00.000Z")
{ "addDays": [{"date": "2018-12-01T00:00:00.000Z"}, 1] } => // Date("2018-12-02T00:00:00.000Z")

back to top

Arithmetic Operators

sum

The sum operator returns the sum of its operands, as a floating-point value. All operands must evaluate to or be convertible to numeric values.

The add operator is an alias for the sum operator.

Examples:

{ "sum": [1, 1] } => 2.0
{ "sum": [1, 1.0, "1"] } => 3.0

back to top

add

The add operator returns the sum of its operands, as a floating-point value. All operands must evaluate to or be convertible to numeric values.

The add operator is an alias for the sum operator.

Examples:

{ "add": [1, 1] } => 2.0
{ "add": [1, 1.0, "1"] } => 3.0

back to top

subtract

The subtract operator returns its second operand subtracted from its first operand, as a floating-point value. Both operands must evaluate to or be convertible to numeric values.

Examples:

{ "subtract": [2, 1] } => 1.0
{ "subtract": [1, 2.0] } => -1.0

back to top

Collection Operators

value

The value operator is used to "quote" object-literal values within a filter expression so they are not misinterpreted as operators during evaluation. The operand(s) will be returned unmodified and unevaluated.

Examples:

{ "value": {"example": 123} } => {"example": 123}
{ "value": [{"example": 123}] } => [{"example": 123}]

back to top

contains

The contains operator returns true if its first operand contains its second operand. If the first operand evaluates to an array, the array will be searched for the second operand. If the first operand evaluates to a string, the string will be searched for the second operand which must also evaluate to a string.

Examples:

{ "contains": ["example", "amp"] } => true
{ "contains": [[1, 2, 3], 2] } => true

back to top

overlaps

The overlaps operator returns true if its first operand overlaps/intersects with its second operand. Both operands must evaluate to arrays.

Examples:

{ "overlaps": [["x", "y", 123], [123, "y"]] } => true
{ "overlaps": [["x", "y", "z"], ["a", "b", "c"]] } => false

back to top

groupBy

The groupBy operator returns an object containing grouped arrays of objects, using a path string to lookup the grouping property for each item in a given array. The first operand must evaluate to a path string and the second operand must evaluate to an array of objects to apply the groupings over. The property values used for grouping will be converted to strings for their grouping keys within the returned object.

Example:

{
  "groupBy": [
    "$.groupId",
    [
      {"value": {"groupId": "A", "id": 1}},
      {"value": {"groupId": "A", "id": 2}},
      {"value": {"groupId": "B", "id": 3}}
    ]
  ]
}
=> {
  "A": [{"groupId": "A", "id": 1}, {"groupId": "A", "id": 2}],
  "B": [{"groupId": "B", "id": 3}]
}

back to top

Conditional Operators

ifElse

The ifElse operator returns its second operand if its first operand evaluates to true, otherwise its third operand is returned. The first operand must evaluate to a boolean. The ifElse operator is equivalent to a ternary conditional.

Examples:

{"ifElse": [true, "a", "b"] } => "a"
{"ifElse": [false, "a", "b"] } => "b"

back to top

Contextual Operators

path

The path operator returns a value within an implicit or given JSON object or array, using JSON-Path syntax for path lookups. If a single operand evaluating to a path string is provided, the path will be used to lookup a value within the resource associated with the current expression. If an array of operands is provided, the second operand should evaluate to an object or array which the first operand is a path into.

Examples:

{ "path": "$.name" } => // value of the "name" field within the given input resource
{ "path": ["$.items[0].name", {"value": {"items": [{"name": "example"}]}}] } => "example"

back to top

with

The with operator binds values to names within a new evaluation context, evaluates its final operand under the prepared context, then finally unbinds the temporary evaluation context and returns the evaluated result. An array of operands must be provided where all leading operands are object-literal values and the final operand is any expression.

The with operator is special, in that its leading operands are received as object-literal values but are not interpreted as operations. Instead, the objects are interpreted as key-value bindings where each value is evaluated as an expression and assigned to its key/name within the current evaluation context. The final operand will be evaluated and returned.

Each with operation creates a nested evaluation context which inherits the bindings of its parent context. This is conceptually similar to lexical/block scoping. Leading operands will be evaluated in order; each leading operand will add bindings to the context and may reference values bound by prior operands or bound within parent contexts.

If a binding object contains multiple binding properties, the bindings in the object may not reference each other, but may reference pre-existing bindings.

The ref operator may be used to reference values which are bound within the current context or any parent context. The combination of the with and ref operators enables a sequential programming style and re-use of intermediate values. For complex expressions, this can be helpful for overall readability and terseness.

Examples:

{
  "with": [
    {"x": 1.0, "y": 1.0},
    {"add": [{"ref": "x"}, {"ref": "y"}]}
  ]
}
=> 2.0

// Equivalent:
//   (x, y) = (1.0, 1.0)
//   return x + y
{
  "with": [
    {"x": 1.0},
    {"y": 1.0},
    {"x": {"add": [{"ref": "x"}, {"ref": "y"}]}},
    {"ref": "x"}
  ]
}
=> 2.0

// Equivalent:
//  x = 1.0
//  y = 1.0
//  x = x + y
//  return x
{
  "with": [
    {"x": 1.0},
    {"with": [
      {"y": 1.0},
      {"add": [{"ref": "x"}, {"ref": "y"}]}
    ]}
  ]
}
=> 2.0

// Equivalent:
//   x = 1.0
//   (
//     y = 1.0
//     return x + y
//   )

back to top

ref

The ref operator references a named value bound within the current evaluation context. The operand must be a string-literal: the name of the referenced binding. If the same name has been bound multiple times within the current evaluation context, the most-recently bound value will be returned.

The with operator must be used to bind named values within an evaluation context before they may be referenced by the ref operator.

Examples:

{
  "with": [
    {"name": "example", "id": 123},
    {"format": ["{0}-{1}", [{"ref": "name"}, {"ref": "id"}]]}
  ]
}
=> "example-123"

// Equivalent:
//   (name, id) = ("example", 123)
//   return format("{0}-{1}", [ name, id ])
{
  "with": [
    {"list": [1, 2, 3]},
    {"first": {"path": ["$[0]", {"ref": "list"}]}},
    {"firstPlusOne": {"add": [{"ref": "first"}, 1.0]}},
    {"ref": "firstPlusOne"}
  ]
}
=> 2.0

// Equivalent:
//  list = [1, 2, 3]
//  first = list[0]
//  firstPlusOne = first + 1.0
//  return firstPlusOne

back to top

Last updated