Every Branch Is a Value
Conditionals the DataWeave way — if/else expressions, match/case with value, type, and regex patterns, the default operator, and the do scope for readable branching.
In an imperative language an if is a fork in the road: you go one way or the other, and something happens down each path. DataWeave has no paths, because it has no statements — an if here doesn’t do anything, it is a value. if (x) a else b evaluates to a or to b, and that value slots into whatever expression contains it. Once that clicks, every conditional in this post is just a different way of choosing which value.
if / else, and the ladder
The basic form is an expression, so it needs both branches — there is no if without an else:
%dw 2.0
output application/json
---
if (payload.total > 100) "free" else "standard"
Chain them with else if when you have more than two outcomes:
%dw 2.0
output application/json
var total = payload.total
---
if (total >= 500) "platinum"
else if (total >= 100) "gold"
else "standard"
That reads fine at three rungs. At six it becomes a ladder you have to climb to understand, and that’s the signal to reach for match.
match / case
match tests a value against a series of case patterns and evaluates to the right-hand side of the first one that fits. The simplest patterns are literal values:
%dw 2.0
output application/json
---
payload.status match {
case "shipped" -> "On its way"
case "delivered" -> "Complete"
case "cancelled" -> "Refunded"
else -> "Processing"
}
The else is the default arm — it catches anything the cases above didn’t. Leave it off and an unmatched value is an error, so else is rarely optional in practice.
Type patterns
A case can match on type instead of value with is. This is how you write one transform that copes with a field arriving as either a number or a stringified number — the sort of thing loosely-typed upstream systems love to do:
%dw 2.0
output application/json
---
payload.price match {
case is Number -> payload.price
case is String -> payload.price as Number
else -> 0
}
Regex patterns
matches tests the value against a regular expression — perfect for validating the shape of an identifier like a SKU:
%dw 2.0
output application/json
---
payload.sku match {
case matches /^[A-Z]-\d{3}$/ -> "valid"
else -> "unrecognized format"
}
(This case matches pattern isn’t the standalone matches operator that returns a plain Boolean — same keyword, two different tools.)
Binding and guards
The most useful form binds the matched value to a name and guards it with if. This is else if reborn as pattern matching, and it flattens that ladder into something you can scan:
%dw 2.0
output application/json
---
payload.total match {
case t if (t >= 500) -> "platinum"
case t if (t >= 100) -> "gold"
case t if (t > 0) -> "standard"
else -> "empty cart"
}
t is the value being matched, available on the right-hand side. Read the cases top to bottom — the first guard that’s true wins, so order them from most to least specific.
default: the null-safety shortcut
Upstream data is full of holes, and default fills them. x default y evaluates to x unless x is null or absent, in which case it evaluates to y:
%dw 2.0
output application/json
---
{
coupon: payload.coupon default "NONE",
giftWrap: payload.giftWrap default false,
notes: payload.notes default ""
}
It chains, too — a default b default c walks left to right until something isn’t null. Reaching for if (x != null) x else y when x default y says the same thing is a tell that you’re still thinking imperatively.
Boolean operators short-circuit
and, or, and not behave the way you’d hope. and stops as soon as it hits a false, or stops at the first true — so you can guard a dereference with the check that makes it safe:
%dw 2.0
output application/json
---
(payload.items != null) and (sizeOf(payload.items) > 0)
If items is null the left side is false and the sizeOf never runs. There’s also unless, the mirror of if for conditional fields — the reshaping post used (field: value) if (cond), and (field: value) unless (cond) includes the field in the opposite case.
do: giving a branch some room
Branches often need a little scratch space — a subtotal here, a helper there — and cramming it all into one expression gets dense. The do scope carves out a local block where you can declare vars and funs, ending in a single --- and the value it produces:
%dw 2.0
output application/json
fun classify(order) = do {
var items = order.items default []
var subtotal = sum(items map (i) -> i.price * i.qty)
var shipping = if (subtotal >= 100) 0 else 9.99
---
{
subtotal: subtotal,
shipping: shipping,
total: subtotal + shipping,
tier: subtotal match {
case s if (s >= 500) -> "platinum"
case s if (s >= 100) -> "gold"
else -> "standard"
}
}
}
---
payload map classify
The vars are visible only inside the do, so nothing leaks into the header, and the conditional logic reads in one pass instead of as one enormous nested expression. do is the difference between a clever one-liner and something a colleague can maintain.
Final thoughts
Conditionals are where the “it’s a functional language” framing pays off most concretely. Because if and match are values rather than control flow, they nest anywhere a value is allowed — inside an object field, an array element, a function argument — and you never manage a mutable result variable you assign to across branches. Pick literals for fixed sets, types for messy inputs, guards for ranges, and default for the holes. That’s the whole vocabulary, and it composes.
Next: Stop Copy-Pasting Your Transforms — headers, the do scope, importing the standard library, and writing your own modules.
Comments