Skip to main content

Objects

The Objects category takes an object apart into its keys, values, or entries, tests its entries with a predicate, and merges two objects with a conflict resolver. Each function is callable in prefix form fn(obj, …) or postfix/UFCS form obj fn(…).

Several object operations you might expect here live in the Core category instead, because they're part of the general toolkit: keysOf, valuesOf, namesOf, entriesOf, mapObject, and filterObject. See Core.

An entry is the object's view of a single key/value pair. entrySet materializes entries as objects of the shape {key, value, attributes}attributes is reserved for values that carry metadata and is an empty object {} for plain values. The predicate functions (everyEntry, someEntry) instead pass the raw (value, key) to your lambda rather than an entry object.

Extraction

These four functions all extract from an object into an array. They differ in what they extract:

  • entrySet → full entries ({key, value, attributes} objects)
  • keySet → the keys, as-is (a non-string key stays in its original type)
  • nameSet → the keys, coerced to strings
  • valueSet → the values
FunctionSignatureDescriptionExample
entrySet(:object?)Array of entry objects, each {key, value, attributes}. attributes is {} for plain values.{a: 1, b: 2} entrySet()[{"key": "a", "value": 1, "attributes": {}}, {"key": "b", "value": 2, "attributes": {}}]
keySet(:object?)Array of the object's keys, in their original type.{a: 1, b: 2} keySet()["a", "b"]
nameSet(:object?)Array of the object's keys coerced to strings.{a: 1, b: 2} nameSet()["a", "b"]
valueSet(:object?)Array of the object's values, in key order.{a: 1, b: 2} valueSet()[1, 2]

keySet vs nameSet: for an object whose keys are already strings the two are identical. They diverge only when keys aren't strings — e.g. an object whose keys are symbols yields [:a, :b] from keySet but ["a", "b"] from nameSet.

All four are null-safe: called on null (the receiver type is :object?), each returns an empty array [].

Predicates

Both take a (value, key) predicate lambda. With $ shorthand, $ is the value and $$ is the key.

FunctionSignatureDescriptionExample
everyEntry(:object?, :lambda)True if the predicate holds for every entry. Vacuously true for an empty or null object.{a: 2, b: 4} everyEntry((v, k) -> v mod 2 == 0)true
someEntry(:object?, :lambda)True if the predicate holds for at least one entry. false for an empty or null object.{a: 1, b: 4} someEntry((v, k) -> v > 3)true
{a: 2, b: 4} everyEntry($ > 0)            // true   — $ is the value
{a: 1, x_temp: 2} someEntry($$ == "x_temp") // true — $$ is the key

Merging

FunctionSignatureDescriptionExample
mergeWith(:object, :object, :lambda)Merges two objects into one. For each key present in either object, the resolver lambda (left, right, key) decides the value.mergeWith({a: 1, b: 2}, {b: 20, c: 30}, (l, r, k) -> l + r){"a": 1, "b": 22, "c": 30}

The resolver runs for every key in the union of both objects, not only the conflicting ones. When a key exists on only one side, the other side is passed as null. So a resolver that adds (l + r) would fail on a non-overlapping key; guard it, or use the resolver to express a precedence rule:

// Right object wins on conflicts, fall back to left otherwise:
mergeWith({a: 1, b: 2}, {b: 9}, (l, r, k) -> r default l)   // {"a": 1, "b": 9}

// $ is left, $$ is right, $$$ is the key:
mergeWith({a: 1}, {a: 5}, $ + $$)                            // {"a": 6}

mergeWith is the single merge primitive. It always drives the conflict decision through your resolver lambda rather than defaulting to "right wins."

Composing

Because the extraction functions return arrays, they thread naturally into Core array functions:

{a: 1, b: 2} entrySet() map($.value * 10)   // [10, 20]