Skip to main content

collections


title: Collections sidebar_position: 2

Collections

Part of the function and operator catalog. Every example shown below is executed as an assertion by the engine's own test suite (examples.test.ts), so a result shown here is a result the engine actually produces for that input.

GROUPBY

GROUPBY(collection: an array of any value, fieldName: text) -> an array of an object

Groups a collection's elements by a top-level field's value, returning an array of { key, items } objects. Groups appear in the order each key first appears in the input, and items within a group appear in input order. The key must already be a string — a non-string key is a coded error, never coerced, so a number is never silently converted into a string that decides which rows land together. Keys are compared as exact UTF-16 code-unit sequences with no Unicode normalization. Addresses a top-level field by name only, not a nested path or a computed key; filter or reshape the collection first if the key you need is not top-level.

Examples:

  • GROUPBY(input.xs, 'k')
    • context: {"xs":[{"k":"a","n":1},{"k":"b","n":2},{"k":"a","n":3}]}
    • result: [{"key":"a","items":[{"k":"a","n":"1"},{"k":"a","n":"3"}]},{"key":"b","items":[{"k":"b","n":"2"}]}]
  • GROUPBY(input.xs, 'k')
    • context: {"xs":[{"k":1}]}
    • fails with EXPR_GROUP_KEY_NOT_STRING

FIND_BY

FIND_BY(collection: an array of any value, fieldName: text, value: any value) -> any value

Returns the single element whose named top-level field equals the given value, decided through the one equality predicate. Zero matches and several matches are both coded errors, the same as GET — first-wins is rejected outright, because under it the same data in a different order books differently and nothing in the trace records that a choice was made. Addresses a top-level field by name only, not a nested path or a computed key; filter or reshape the collection first if the key you need is not top-level.

Examples:

  • FIND_BY(input.xs, 'id', 2)
    • context: {"xs":[{"id":1},{"id":2}]}
    • result: {"id":"2"}
  • FIND_BY(input.xs, 'id', 9)
    • context: {"xs":[{"id":1}]}
    • fails with EXPR_NO_MATCH
  • FIND_BY(input.xs, 'id', 1)
    • context: {"xs":[{"id":1},{"id":1}]}
    • fails with EXPR_MULTIPLE_MATCHES

GET

GET(collection: an array of any value) -> any value

Returns the sole element of a one-element collection. A filter always returns an array; GET is how an author asserts that array holds exactly one thing. Zero elements is a coded no-match error, and more than one is a coded multiple-matches error — GET never picks first, because under first-wins the same data in a different order would book differently with nothing in the trace recording that a choice was made.

Examples:

  • GET(input.xs)
    • context: {"xs":[5]}
    • result: "5"
  • GET(input.xs)
    • context: {"xs":[]}
    • fails with EXPR_NO_MATCH
  • GET(input.xs)
    • context: {"xs":[1,2]}
    • fails with EXPR_MULTIPLE_MATCHES

ARGS

ARGS(first: any value, ...) -> an array of any value

Builds an array out of its arguments, in order — this is how an expression writes an array literal; there is no bracket syntax for one. There is no zero-argument form: every aggregation over an empty collection is a coded error, so an empty array literal could only ever be a way of writing an expression that fails. A filter is how an author gets a possibly-empty array.

Examples:

  • ARGS(1)
    • result: ["1"]
  • ARGS(1, 'a', true)
    • result: ["1","a",true]

SLICE

SLICE(collection: an array of any value, start: a number, end: a number) -> an array of any value

Returns the elements of a collection from start (inclusive) to end (exclusive), both counted in element positions. Both bounds must be whole numbers between zero and the collection's length inclusive — an end exactly at the length resolves (it reaches the last element), and one past it is a coded out-of-range error, the same as start. A start equal to end returns an empty array.

Examples:

  • SLICE(input.xs, 1, 3)
    • context: {"xs":[1,2,3,4]}
    • result: ["2","3"]
  • SLICE(input.xs, 0, 3)
    • context: {"xs":[1,2,3]}
    • result: ["1","2","3"]
  • SLICE(input.xs, 1, 1)
    • context: {"xs":[1,2,3]}
    • result: []
  • SLICE(input.xs, 0, 4)
    • context: {"xs":[1,2,3]}
    • fails with PATH_INDEX_OUT_OF_RANGE

LENGTH

LENGTH(value: any value) -> a number

Returns a string's length in UTF-16 code units — not its grapheme-cluster count. A string holding one astral character (for example an emoji) has length two, and a precomposed and a decomposed spelling of the same grapheme have different lengths. Returns an array's element count. Any other kind of value — a number, a boolean, a date, an object or null — is a type error.

Examples:

  • LENGTH('ab')
    • result: "2"
  • LENGTH('')
    • result: "0"
  • LENGTH('😀')
    • result: "2"
  • LENGTH(input.xs)
    • context: {"xs":[1,2,3]}
    • result: "3"
  • LENGTH(input.xs)
    • context: {"xs":[]}
    • result: "0"
  • LENGTH(1)
    • fails with EXPR_TYPE_MISMATCH

INCLUDES

INCLUDES(collection: an array of any value, value: any value) -> true/false

Answers whether a collection includes a value, deciding membership through the engine's one equality predicate — no second rule. A collection holding the string '2' does not include the number 2. String comparison is exact UTF-16 code units with no Unicode normalization, so a precomposed and a decomposed spelling of the same grapheme are different members.

Examples:

  • INCLUDES(input.xs, 2)
    • context: {"xs":[1,2,3]}
    • result: true
  • INCLUDES(input.xs, 4)
    • context: {"xs":[1,2,3]}
    • result: false
  • INCLUDES(input.ys, 2.0)
    • context: {"ys":["2"]}
    • result: false

JOIN

JOIN(collection: an array of any value, separator: text) -> text

Joins a collection's string elements with a separator. Every element must already be text (a coded error otherwise) — an engine-chosen decimal or date format must never reach output text a customer reads without anyone choosing it; convert each element explicitly first. Joining an empty collection is the empty string, since joining nothing has one obvious answer.

Examples:

  • JOIN(input.xs, ', ')
    • context: {"xs":["a","b","c"]}
    • result: "a, b, c"
  • JOIN(input.xs, ',')
    • context: {"xs":[]}
    • result: ""
  • JOIN(input.xs, ',')
    • context: {"xs":["a",1]}
    • fails with EXPR_JOIN_NON_STRING