Skip to content

Operators ​

Operators are the building blocks of every UTL-X expression. This chapter covers all 32 operators — arithmetic, comparison, logical, null handling, access, construction, and flow — with examples and precedence rules. Chapter 14 (Expressions and Operators) builds on this reference, covering precedence in depth and how operators combine in practical expression patterns.

Arithmetic Operators ​

Standard arithmetic on numbers:

utlx
10 + 3       // 13       addition
10 - 3       // 7        subtraction
10 * 3       // 30       multiplication
10 / 3       // 3.333... division (always floating point)
10 % 3       // 1        modulo (remainder)
2 ** 10      // 1024     exponentiation
-42          // -42      unary minus (negation)

Division always produces a floating-point result — 10 / 3 is 3.333..., not 3. There is no integer division operator. Use floor(10 / 3) if you need integer results.

Division by zero throws a runtime error. Guard if the divisor might be zero:

utlx
if (count > 0) total / count else 0
OperatorNameExampleResult
+Addition10 + 313
-Subtraction10 - 37
*Multiplication10 * 330
/Division (always float)10 / 33.333...
%Modulo (remainder)10 % 31
**Exponentiation2 ** 101024
- (prefix)Unary negation-42-42

Comparison Operators ​

Compare values and produce booleans:

utlx
10 == 10     // true     equality
10 != 5      // true     inequality
10 > 5       // true     greater than
10 < 5       // false    less than
10 >= 10     // true     greater or equal
10 <= 5      // false    less or equal

String comparison is lexicographic (alphabetical) and case-sensitive:

utlx
"apple" < "banana"    // true
"Alice" == "alice"    // false (case-sensitive)
"10" < "9"            // true (string comparison: "1" < "9")

Null comparisons:

utlx
null == null   // true
null != "abc"  // true
null == 0      // false (null is not zero)
null == ""     // false (null is not empty string)
OperatorNameExampleResult
==Equality10 == 10true
!=Inequality10 != 5true
<Less than3 < 5true
<=Less or equal5 <= 5true
>Greater than10 > 5true
>=Greater or equal10 >= 10true

Logical Operators ​

Combine boolean expressions:

utlx
true && false    // false    logical AND
true || false    // true     logical OR
!true            // false    logical NOT

Short-circuit evaluation: && stops at the first false, || stops at the first true. This is useful for guarding expressions:

utlx
// Safe: count() only evaluates if items is not null
$input.items != null && count($input.items) > 0

Truthy and Falsy Values ​

In boolean contexts, UTL-X treats these as falsy:

  • false

  • null

  • 0

  • "" (empty string)

Everything else is truthy, including empty arrays [] and empty objects {}.

OperatorNameExampleResult
&&Logical AND (short-circuit)true && falsefalse
``Logical OR (short-circuit)
!Logical NOT!truefalse

Null Handling Operators ​

Two operators for dealing with null values — the most common source of runtime errors in data transformation:

Nullish Coalescing (??) ​

Provide a default value when an expression is null:

utlx
$input.nickname ?? $input.name ?? "Anonymous"
// First non-null value wins

Evaluates left to right. Essential for handling optional fields:

utlx
{
  currency: $input.currency ?? "EUR",
  country: $input.address?.country ?? "NL",
  discount: $input.promoCode ?? "NONE"
}

Safe Navigation (?.) ​

Access properties on potentially null values without throwing errors:

utlx
$input.order?.customer?.address?.city
// Returns null if any step is null — no error

Without ?., a null intermediate value causes a runtime error. With ?., null propagates silently.

Combine ?. with ?? for safe access with defaults:

utlx
$input.order?.customer?.name ?? "Unknown Customer"
OperatorNameExampleResult
??Nullish coalescingnull ?? "default""default"
?.Safe navigationnull?.namenull (no error)

Access and Navigation Operators ​

These operators navigate the UDM tree — accessing properties, attributes, metadata, and children:

utlx
$input.name                      // dot: property access
$input["content-type"]           // bracket: dynamic key or special characters
$input[0]                        // bracket: array index
$input.*                         // wildcard: all children as array
$input.@id                       // at: XML attribute
$input.@*                        // at-wildcard: all attributes as array
$input.^schemaType               // caret: metadata

Each is a different kind of navigation:

OperatorWhat it accessesExample
.nameNamed property$input.Order.Customer
["key"]Property by string (dynamic or special chars)$input["cbc:ID"]
[0]Array element by index$input.items[0]
.*All child properties as array$input.IDOC.*
.@nameXML attribute by name$input.Order.@id
.@*All XML attributes as array$input.Order.@*
.^keyInternal metadata by key$input.^xsdPattern

The . navigates data (what the user sent). The @ navigates XML attributes (format-specific metadata on elements). The ^ navigates internal metadata (parser-generated, never in output). See Chapter 8 for detailed examples of each.

Data Construction Operators ​

Spread (...) ​

Copy all properties from one object into another, or concatenate arrays:

utlx
{...baseObject, status: "CONFIRMED"}     // copy + override
{...obj1, ...obj2}                        // merge (later wins)
[...array1, ...array2]                    // concatenate arrays

Spread is shallow — nested objects are referenced, not deep-copied. See Chapter 8 for the full spread operator guide.

Computed Property Names ([expr]:) ​

Use an expression result as a property name:

utlx
let field = "fullName"
{[field]: concat($input.first, " ", $input.last)}
// {"fullName": "Alice Johnson"}

The brackets evaluate the expression and use the result as the key. See Chapter 26 (dynamic keys) for advanced patterns.

Flow Operators ​

Pipe (|>) ​

Pass the result of the left expression as the first argument to the right function. Reads left-to-right instead of nested inside-out:

utlx
// Without pipe — read from inside out:
sort(unique(map($input.items, (item) -> item.category)))

// With pipe — read left to right:
$input.items
  |> map((item) -> item.category)
  |> unique()
  |> sort()

Both produce the same result. The pipe version reads like a recipe: "take items, map to categories, remove duplicates, sort."

Lambda Arrows (-> and =>) ​

Define anonymous functions (lambdas). Both arrows are equivalent:

utlx
// Thin arrow (standard):
(x) -> x * 2
(x, y) -> x + y

// Fat arrow (JavaScript-style):
(x) => x * 2
x => x * 2          // single param: parentheses optional with =>

The -> is the convention used throughout this book. The => is accepted for developers coming from JavaScript/TypeScript. The => also appears in match expression arms:

utlx
match ($input.status) {
  "ACTIVE" => "green",
  "PENDING" => "yellow",
  _ => "gray"
}

See Chapter 15 for the full lambda and function guide.

Binding (=) ​

Used in let bindings to assign a value to a name:

utlx
let total = $input.quantity * $input.price
let taxRate = 0.21
let tax = total * taxRate

The = is NOT assignment (there are no mutable variables in UTL-X). It is a one-time binding — the name refers to the value for the rest of its scope. See Chapter 8 for let binding scoping rules.

String Concatenation ​

UTL-X does NOT use + for string concatenation — use the concat() function:

utlx
concat("Hello", " ", "World")     // "Hello World"

This is deliberate. + is arithmetic only. Mixing + for both numbers and strings leads to ambiguity ("3" + 4 — is it "34" or 7?). UTL-X avoids this by keeping arithmetic and string operations separate.

For joining arrays of strings: join(array, separator).

Operator Precedence ​

From highest to lowest precedence. Higher precedence binds tighter:

PrecedenceOperatorsAssociativity
1 (highest)Postfix: . ?. [] () @ ^Left
2Unary: ! -Right
3Exponentiation: **Right
4Multiplicative: * / %Left
5Additive: + -Left
6Comparison: < > <= >=Left
7Equality: == !=Left
8Logical AND: &&Left
9Logical OR: `
10Nullish coalescing: ??Left
11Ternary: ? :Right
12Pipe: `>`
13 (lowest)Conditional: if/elseRight

When in doubt, use parentheses. They make intent explicit and prevent precedence surprises:

utlx
// Ambiguous: does && bind tighter than ||?
a || b && c        // means: a || (b && c) — && is higher

// Clear: parentheses make intent obvious
(a || b) && c      // different meaning, explicit

Reserved Operator ​

OperatorNameStatus
..Recursive descentToken defined, parser not implemented. Reserved for future use.

Released under AGPL-3.0.