Appendix D: Library and Behavior Reference
This reference collects the detailed conversion, selection, scope and library investigations that are useful once you can follow the main examples.
This reference collects the detailed conversion, selection, scope and library investigations that are useful once you can follow the main examples. Read an entry to answer a specific question, and keep its fixture beside the script. The main chapters teach the operations needed by the running report before using them.
Dots, brackets, and one hyphen
The workhorse is the dot. payload.orderId selects a key from an object. Dots chain, so payload.customer.email walks two levels down. Arrays take an index in brackets, zero-based, and a negative index counts from the end:
Example 284 — Combine object paths and array indexes.
Input payload — order.json:
{
"orderId": "A-1001",
"customer": { "name": "Dana", "email": "dana@example.com", "tier": "gold" },
"shipping": { "method": "courier", "price": 4.0 },
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
]
}
%dw 2.0
output application/json
---
{
id: payload.orderId,
email: payload.customer.email,
first: payload.items[0].sku,
last: payload.items[-1].sku
}
{
"id": "A-1001",
"email": "dana@example.com",
"first": "PEN-01",
"last": "CLP-08"
}
The bracket form works on objects too, with a string inside, and the string can be any expression. payload["orderId"] is the same selection as payload.orderId. Between the brackets you can put a variable, a computed name, or a key containing a space or hyphen, which the dot form cannot take as a bare name.
Example 285 — Select fields with brackets.
Use order.json as payload, as above.
%dw 2.0
output application/json
var field = "tier"
---
{
plain: payload["orderId"],
chained: payload["customer"]["email"],
computed: payload.customer[field],
hyphenated: { "ship-to": "Lisbon" }["ship-to"]
}
{
"plain": "A-1001",
"chained": "dana@example.com",
"computed": "gold",
"hyphenated": "Lisbon"
}
A hyphen changes how the dot form is parsed. { "ship-to": "Lisbon" }.ship-to becomes a subtraction: select ship, which is missing and therefore null, then subtract the to range function. The key exists in the object, but this expression never asks for it. Instead, the runtime reports that none of its - overloads accepts those arguments:
[ERROR] Error while executing the script:
[ERROR] Unable to call: `-` with arguments: (`Null`, (from: Number, to: Number) -> Range).
Reasons:
- Expecting Type: `Array<T>`, but got: `Null`.
|-- From: `Array<T>`
|- From: -<T>(lhs: Array<T>, rhs: Any) -> Array<T>
4| { "ship-to": "Lisbon" }.ship-to
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Expecting Type: `Number`, but got: `Null`.
|-- From: `Number`
- Expecting Type: `Number`, but got: (from: Number, to: Number) -> Range.
|-- From: `Number`
|- From: -(lhs: Number, rhs: Number) -> Number
4| { "ship-to": "Lisbon" }.ship-to
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 17 more options ...
4| { "ship-to": "Lisbon" }.ship-to
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Location:
03_hyphen_dot_fails (line: 4, column:1)
The twenty-line subtraction error comes from the key’s punctuation. to builds ranges, which is why the message mentions Range. Had the key been ship-date, the failure would have been Unable to resolve reference of: date, at compile time, before anything ran. Brackets keep these names intact: a key that a downstream system spells with a hyphen, dot or space is selected with ["..."].
The missing-key rule, and where it stops
Selecting a key that is not there returns null. Selecting anything from null returns null. Those rules let a chain continue through absent intermediate fields: payload.shippingAddress is null, and .city on null is null again. The chain is safe all the way down.
Example 286 — Continue a selection through null.
Use order.json as payload, as above.
%dw 2.0
output application/json
---
{ onNull: null.anything, chained: payload.warehouse.bay.shelf, onNumber: (42).anything, onArrayOfNumbers: [1, 2].anything }
{
"onNull": null,
"chained": null,
"onNumber": null,
"onArrayOfNumbers": null
}
A key selection on a number is null too; payload.shipping.price.amount therefore returns null when price is numeric. Selecting a key from an array of numbers also gives null, because none of the elements is an object. So far every wrong turn ends in null. The one that does not is a string:
Example 287 — Try selecting an object field from text.
Use order.json as payload, as above.
%dw 2.0
output application/json
---
{ onString: payload.customer.name.anything }
[ERROR] Error while executing the script:
[ERROR] You called the function 'Value Selector' with these arguments:
1: String ("Dana")
2: Name ("anything")
But it expects one of these combinations:
(Array, Name)
(Array, String)
(Date, Name)
(DateTime, Name)
(LocalDateTime, Name)
(LocalTime, Name)
(Object, Name)
(Object, String)
(Period, Name)
(Time, Name)
4| { onString: payload.customer.name.anything }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Trace:
at 287-select-on-string-fails::main (line: 4, column: 13) at:
4| { onString: payload.customer.name.anything }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The error exposes the two arguments to Value Selector: the value on the left of .name and the key on the right. Its overloads accept objects, arrays and temporal types; |2026-09-13|.year, for example, selects from a date, as chapter 22 shows. Null and Number fall through to null, while String matches nothing and fails. A chain can therefore continue safely through a missing key without being safe for every unexpected input type. Check both the path and the kind of value it reaches.
As chapter 2 demonstrated, a typo in a key name is not a syntax error, a runtime error or a warning. It is a null in the output, and it stays there until someone reads the output. When a required field comes back null, check the key name first. Then check whether an earlier segment of the chain was already null.
Indexing has its own small surprises. An index on an object gives the nth value; on a string it gives the nth character; on null, null:
Example 288 — Apply indexes to different value types.
Use order.json as payload, as above.
%dw 2.0
output application/json
---
{ indexOnObject: payload.customer[0], indexOnString: payload.customer.name[0], indexOnNull: payload.warehouse[0] }
{
"indexOnObject": "Dana",
"indexOnString": "D",
"indexOnNull": null
}
An index on a number even reads its digits as text: payload.shipping.price[0] is "4". In an order transform, a result like that is a reason to inspect the value the index reached. The selector may be acting on a scalar where you intended to reach an array; the input need not be corrupted for the expression to produce a surprising result.
Coercion with as
as converts a value to a type, and reads left to right: value, as, type.
Example 289 — Convert scalar values explicitly.
Input payload — order.json:
{
"orderId": "A-1001",
"customer": "Dana",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
]
}
%dw 2.0
output application/json
---
{
fromString: "42" as Number,
decimalString: "2.50" as Number,
toString: 42 as String,
decimalToString: 6.0 as String,
productToString: (2.5 * 4) as String,
flag: "true" as Boolean,
upperFlag: "TRUE" as Boolean,
alreadyNumber: 42 as Number
}
{
"fromString": 42,
"decimalString": 2.5,
"toString": "42",
"decimalToString": "6",
"productToString": "10",
"flag": true,
"upperFlag": true,
"alreadyNumber": 42
}
"2.50" became 2.5 and 6.0 became "6": coercion goes through the number, and a numeric value does not preserve the original text’s decimal formatting. Boolean parsing is case-insensitive. Coercing a value to the type it already has is a no-op, which means as Number is safe to apply to a field that is sometimes text and sometimes not.
What as will not do is guess. Every conversion that has no single right answer is an error, and the error names the value:
Example 290 — Reject nonnumeric text during conversion.
%dw 2.0
output application/json
---
{ n: "hello" as Number }
[ERROR] Error while executing the script:
[ERROR] Cannot coerce String (hello) to Number
4| { n: "hello" as Number }
^^^^^^^^^^^^^^^^^
Trace:
at 290-as-fails-number::main (line: 4, column: 6) at:
4| { n: "hello" as Number }
^^^^^^^^^^^^^^^^^
The same failure shape distinguishes several invalid input forms. "1,234.50" as Number fails, because a thousands separator is a format, not a number. " 42 " as Number fails on the spaces. "42abc" fails. "yes" as Boolean fails, because only true and false (in any case) are booleans. 1 as Boolean fails, because DataWeave has no truthiness and a number is never a boolean. payload.items[0] as String fails with Cannot coerce Object to String, because an object has no canonical text form; if you want JSON text, that is write(value, "application/json"), chapter 17’s subject. The one that matters most for real feeds is this:
Example 291 — Try coercing null to a number.
Use order.json as payload, as above.
%dw 2.0
output application/json
---
{ coupon: payload.coupon as Number }
[ERROR] Error while executing the script:
[ERROR] Cannot coerce Null (null) to Number
4| { coupon: payload.coupon as Number }
^^^^^^^^^^^^^^^^^^^^^^^^
Trace:
at 291-as-null-fails::main (line: 4, column: 11) at:
4| { coupon: payload.coupon as Number }
^^^^^^^^^^^^^^^^^^^^^^^^
The order has no coupon, so chapter 2’s selector rule supplies null. The coercion then fails. Handling this optional field requires a decision about what absence means: supply a default before the coercion ((payload.coupon default "0") as Number, chapter 4), guard with if, or let the missing field stop the script. Choose the behavior the receiving system needs. Without an explicit choice, the first order that omits the field will stop at this coercion.
Format schemas: how to read a string, how to print a value
The feed’s placed is "13/09/2026", and as Date alone rejects it, because the only layout as Date knows without help is ISO 8601:
Example 292 — Reject a date with no declared layout.
Input payload — order_from_feed.json:
{
"orderId": "A-1001",
"customer": "Dana",
"placed": "13/09/2026",
"items": [
{ "sku": "PEN-01", "price": "2.50", "qty": "4" },
{ "sku": "PAD-22", "price": "6.00", "qty": "2" },
{ "sku": "CLP-08", "price": "1.00", "qty": "10" }
]
}
%dw 2.0
output application/json
---
{ d: payload.placed as Date }
[ERROR] Error while executing the script:
[ERROR] Cannot coerce String (13/09/2026) to Date
4| { d: payload.placed as Date }
^^^^^^^^^^^^^^^^^^^^^^
Trace:
at 292-reject-a-date-with-no-declared-layout::main (line: 4, column: 6) at:
4| { d: payload.placed as Date }
^^^^^^^^^^^^^^^^^^^^^^
Converting between text and a structured type often needs a layout. A format schema, in braces after the type, supplies one:
Example 293 — Parse a feed date and add a week.
Use order_from_feed.json as payload, as above.
%dw 2.0
output application/json
---
{
placed: payload.placed as Date {format: "dd/MM/yyyy"},
placedType: typeOf(payload.placed as Date {format: "dd/MM/yyyy"}),
iso: "2026-09-13" as Date,
nextWeek: (payload.placed as Date {format: "dd/MM/yyyy"}) + |P7D|
}
{
"placed": "13/09/2026",
"placedType": "Date",
"iso": "2026-09-13",
"nextWeek": "2026-09-20"
}
placed parsed successfully: typeOf reports Date, and adding seven days produces another date. Yet it printed as 13/09/2026, retaining the input’s layout. The format schema stays attached to the parsed value, and the writer uses that layout when it serialises the value. nextWeek is a different value, produced by the addition, and it carries no schema, so it printed as ISO. To print the parsed value differently, coerce it again:
Example 294 — Inspect a date’s retained format.
Use order_from_feed.json as payload, as above.
%dw 2.0
output application/json
var placed = payload.placed as Date {format: "dd/MM/yyyy"}
---
{
asIs: placed,
toStringNoFormat: placed as String,
toStringIso: placed as String {format: "yyyy-MM-dd"},
afterArithmetic: placed + |P1D|
}
{
"asIs": "13/09/2026",
"toStringNoFormat": "2026-09-13",
"toStringIso": "2026-09-13",
"afterArithmetic": "2026-09-14"
}
as String with no schema gives ISO; as String with one gives that layout. The pattern letters come from Java DateTimeFormatter. dd MMM yyyy produces 13 Sep 2026; EEEE, d MMMM yyyy produces Sunday, 13 September 2026. Chapter 22 covers the remaining patterns. What a wrong layout does is fail at the first field that cannot be satisfied, and the message says which:
Example 295 — Reject a date with the wrong layout.
Use order_from_feed.json as payload, as above.
%dw 2.0
output application/json
---
{ placed: payload.placed as Date {format: "MM/dd/yyyy"} }
[ERROR] Error while executing the script:
[ERROR] Cannot coerce String (13/09/2026) to Date, caused by: Text '13/09/2026' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 13
4| { placed: payload.placed as Date {format: "MM/dd/yyyy"} }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Trace:
at 295-date-parse-wrong-format::main (line: 4, column: 11) at:
4| { placed: payload.placed as Date {format: "MM/dd/yyyy"} }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
That failed because 13 is not a month. "05/09/2026" with the same wrong layout would have parsed to the ninth of May and said nothing, which is the case to fear with day-month ambiguity. The layout is a claim about the feed, and it should come from the feed’s documentation, not from the first sample that happens to parse.
Numbers use the same brace, with Java DecimalFormat patterns, and the schema works in both directions:
Example 296 — Compare numeric formatting patterns.
Use order.json as payload, as above.
%dw 2.0
output application/json
---
{
thousands: 1234.5 as String {format: "#,###.00"},
twoPlaces: (2.5 * 4) as String {format: "0.00"},
optionalDecimals: 10 as String {format: "#.##"},
rounded: 12.4966 as String {format: "0.00"},
parsedBack: "1,234.50" as Number {format: "#,###.00"},
total: sum(payload.items map ($.price * $.qty)) as String {format: "0.00"}
}
{
"thousands": "1,234.50",
"twoPlaces": "10.00",
"optionalDecimals": "10",
"rounded": "12.50",
"parsedBack": 1234.5,
"total": "32.00"
}
parsedBack is the thousands-separator string that as Number refused a few sections ago, accepted once the schema says where the commas go. rounded shows that 0.00 rounds rather than truncating. And total is the answer to “how do I print 32.00”: the number is 32, and the string is a presentation of it.
Formatting must apply to the complete calculation:
Example 297 — Group a conversion before arithmetic.
%dw 2.0
output application/json
---
{
unparenthesised: 2.5 * 4 as String {format: "0.00"},
parenthesised: (2.5 * 4) as String {format: "0.00"},
whatItDid: typeOf(2.5 * 4 as String {format: "0.00"})
}
{
"unparenthesised": 10,
"parenthesised": "10.00",
"whatItDid": "Number"
}
as binds to 4, so 4 as String {format: "0.00"} produces "4.00" before the multiplication. Then * coerces that string back to a number and multiplies it, losing the format. The script runs, but its output contains 10 where the consumer expected "10.00". Parenthesising the product makes the intended sequence visible: calculate the value, then format that result.
Importing the standard library
DataWeave ships a standard library organised into modules under dw::core::: Strings, Arrays, Objects, Numbers, Dates and more. One module, dw::Core, is imported for you, which is why map, sum, sizeOf and upper have worked throughout the collection chapters without a line of setup. Everything else has to be asked for, and a missing import produces an unresolved-reference diagnostic:
Example 298 — Call a function without importing it.
Input payload — orders.json:
[
{ "orderId": "A-1001", "customer": "Dana",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
] },
{ "orderId": "A-1008", "customer": "Femi",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 1 },
{ "sku": "INK-03", "price": 3.0, "qty": 3 }
] }
]
%dw 2.0
output application/json
---
capitalize("gel pen")
[ERROR] Error while executing the script:
[ERROR] Unable to resolve reference of: `capitalize`.
4| capitalize("gel pen")
^^^^^^^^^^
Location:
298-import-not-imported (line: 4, column:1)
capitalize is in dw::core::Strings. There are three forms of import, and they differ in what they put into your namespace.
The wildcard brings in every declaration the module has:
Example 299 — Import every module function.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import * from dw::core::Strings
---
{
mouse: capitalize("wireless mouse"),
pen: capitalize("gel pen"),
snake: capitalize("unit_price")
}
{
"mouse": "Wireless Mouse",
"pen": "Gel Pen",
"snake": "Unit Price"
}
Every word is capitalised, not only the first, and an underscore is treated as a word boundary and replaced by a space. Chapter 16 says the same of capitalize.
The selective form names what you use:
Example 300 — Import selected module functions.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import capitalize, camelize from dw::core::Strings
---
{
label: capitalize("usb-c hub"),
field: camelize("unit_price"),
withSpace: camelize("unit price")
}
{
"label": "Usb C Hub",
"field": "unitPrice",
"withSpace": "unit price"
}
capitalize treats a hyphen as a boundary too, turning "usb-c hub" into three words. camelize uses underscores: "unit_price" becomes unitPrice, while "unit price" comes back unchanged. The lack of an error makes source naming conventions worth checking. If incoming field names contain spaces, replace those with underscores before applying camelize.
The third form imports the module under a name and qualifies every call:
Example 301 — Alias a module import.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import dw::core::Strings as Str
---
{ one: Str::pluralize("order"), many: Str::singularize("boxes") }
{
"one": "orders",
"many": "box"
}
Str::pluralize says at the call site where the function came from. Without the as Str, import dw::core::Strings works the same way with the full name, Strings::pluralize; the alias is for typing.
I use the selective form by default. A wildcard import of Strings puts forty-odd names into scope. A reader has to search the script to tell which ones it uses. A selective import is also the one that fails early when a name is wrong:
Example 302 — Import a function that does not exist.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import capitalise from dw::core::Strings
---
capitalise("gel pen")
[ERROR] Error while executing the script:
[ERROR] Unable to resolve reference of: `capitalise`.
5| capitalise("gel pen")
^^^^^^^^^^
Location:
302-import-unknown-function (line: 5, column:1)
Unable to resolve reference of: `capitalise`.
3| import capitalise from dw::core::Strings
^^^^^^^^^^
Location:
302-import-unknown-function (line: 3, column:8)
Two locations in one error, the call and the import line, because the name resolved at neither. Misspell the module and the message changes shape:
Example 303 — Import a module that does not exist.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import * from dw::core::String
---
capitalize("gel pen")
[ERROR] Error while executing the script:
[ERROR] Unable to resolve module with identifier dw::core::String.
3| import * from dw::core::String
^^^^^^^^^^^^^^^^
Location:
303-import-unknown-module (line: 3, column:15)
Unable to resolve module is the message for a module the runtime cannot find. The same message can identify an incorrect path to a project module.
A local declaration beats an import
If the script declares a function with the same name as one it imported, the script’s own wins:
Example 304 — Inspect a module name collision.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import * from dw::core::Strings
fun capitalize(s) = upper(s)
---
capitalize("gel pen")
"GEL PEN"
No warning that the imported capitalize was shadowed. A local declaration can hide an imported one without a warning. If a local helper needs a name the library uses, the qualified form, Str::capitalize, keeps both reachable.
Numbers: most of the maths is already global
dw::core::Numbers is not where the everyday arithmetic lives, and importing it looking for round is a common first mistake. round, floor, ceil, abs, mod, pow, sqrt, sum, avg, max and min are all in dw::Core, already in scope:
Example 305 — Calculate numeric aggregates and rounding.
Input payload — order.json:
{ "orderId": "A-1001", "customer": "Dana", "coupon": null, "tags": ["gift", null, "rush"],
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4, "note": null },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
] }
%dw 2.0
output application/json
var prices = [19.99, 5.00, 12.50]
---
{
subtotal: sum(prices),
average: avg(prices),
rounded: round(avg(prices)),
withVat: round(sum(prices) * 1.2),
lineTotals: payload.items map ($.price * $.qty),
orderTotal: sum(payload.items map ($.price * $.qty)),
biggest: max(payload.items.qty),
emptySum: sum([]),
emptyMax: max([]),
halves: [2.5, 3.5] map round($)
}
{
"subtotal": 37.49,
"average": 12.49666666666666666666666666666667,
"rounded": 12,
"withVat": 45,
"lineTotals": [
10,
12,
10
],
"orderTotal": 32,
"biggest": 10,
"emptySum": 0,
"emptyMax": null,
"halves": [
3,
4
]
}
The average, empty-array results and rounding each need a different decision from the consumer. avg prints thirty-two decimal places, because Number is arbitrary-precision and the writer prints what it has. If the value is going to a consumer, use the format schema shown earlier. sum([]) is 0 but max([]) is null, so an empty order’s largest quantity needs a default. And round goes half-up on both 2.5 and 3.5, giving 3 and 4. It is not banker’s rounding — a finance consumer that expects 2.5 to round to 2 will disagree with you by one cent, predictably.
avg on nothing is the one that does not return quietly:
Example 306 — Try averaging an empty array.
Use order.json as payload, as above.
%dw 2.0
output application/json
---
{ avgOfNothing: avg([]) }
[ERROR] Error while executing the script:
[ERROR] Division by zero
4709| fun avg(values: Array<Number>): Number = sum(values) / sizeOf(values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Trace:
at dw::Core::avg (line: 4709, column: 42)
at 306-numbers-core-fails::main (line: 4, column: 17) at:
4709| fun avg(values: Array<Number>): Number = sum(values) / sizeOf(values)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The error shows the library’s own source. avg is sum over sizeOf, written in DataWeave, and dividing by zero throws. Much of the library is written in DataWeave itself, and the trace shows you the line when it fails. That is how I learned sumBy is a seeded reduce, as discussed in chapter 13 without opening the docs.
The Numbers module itself is the specialist set, base conversions mostly. With import * from dw::core::Numbers, toHex(255) gave "ff", fromHex("ff") gave 255, toBinary(10) gave "1010", fromBinary("1010") gave 10, toRadixNumber(255, 16) gave "ff" and fromRadixNumber("zz", 36) gave 1295. toOctal does not exist; its import fails the way titleize did, at compile time, naming the function. And round(2.5) in the same script still gave 3: importing Numbers with * did not shadow the global round.
Reaching into a value
Temporal values expose components through selectors. Calendar and clock components shown here are numbers; .timezone returns a TimeZone:
Example 307 — Select temporal components.
%dw 2.0
output application/json
var placedAt = |2026-06-16T14:30:45.250-04:00|
---
{
year: placedAt.year,
month: placedAt.month,
day: placedAt.day,
hour: placedAt.hour,
minutes: placedAt.minutes,
seconds: placedAt.seconds,
nanos: placedAt.nanoseconds,
dayOfWeek: placedAt.dayOfWeek,
dayOfYear: placedAt.dayOfYear,
quarter: placedAt.quarter,
timezone: placedAt.timezone,
offsetSeconds: placedAt.offsetSeconds
}
{
"year": 2026,
"month": 6,
"day": 16,
"hour": 14,
"minutes": 30,
"seconds": 45,
"nanos": 250000000,
"dayOfWeek": 2,
"dayOfYear": 167,
"quarter": 2,
"timezone": "-04:00",
"offsetSeconds": -14400
}
The plurals need care — .minutes, .seconds and .nanoseconds, but .hour and .day. .dayOfWeek is ISO-numbered, Monday 1 through Sunday 7, so 16 June 2026 is a Tuesday. A wrong name produces no error:
Example 308 — Try the singular minute selector.
%dw 2.0
output application/json
var placedAt = |2026-06-16T14:30:00-04:00|
---
{ minute: placedAt.minute }
{
"minute": null
}
A selector on a temporal value follows the object-selector rule: an unknown key is null. .minute therefore compiles and runs, reporting every order as placed at minute null. The error reaches the consumer as data.
How to find the other two hundred
These examples cover perhaps forty functions. The DataWeave 2.12.3 module sources bundled with Mule 4.12.3 name 153 functions across the dw::core::* modules and another 81 in dw::Core itself. The module’s first-argument type and each function’s signature help you find the rest without memorising the catalogue. Three habits make that search easier.
Read the module page once. The modules split by the type they operate on — strings in Strings, arrays in Arrays, objects in Objects, numbers in Numbers, dates in Dates and Periods (chapter 22). Guess the module from the type of the first argument and skim its page. You will recognise firstWith faster than you would write it.
Start with the signature, then check the edge cases. sumBy(array: Array<T>, selector: (T) -> Number): Number describes the call in one line: an array, a function that pulls a number out of each element, a number back. The prose fills in behavior that signature cannot tell you. This chapter has shown which cases to look for — the empty array, the null, the missing key.
Let the error text be the reference. The messages above listed every overload of an operator and showed the library’s own source. A selective import of a name you are not sure of fails at compile time and says so. Guess the name, import it, run it: that loop is faster than any search box, and it is the loop chapter 15 builds the test workflow on.
Exercises
Find the coercion. Without running it, say what type each of these has: "6.00" * 1, "6.00" ++ "", "6.00" == 6, ("6.00" as Number) == 6. Then run them and check.
Show answer
Number (the * coerced), String (++ concatenated an empty string), Boolean and false (no coercion on ==, so a string is compared to a number), Boolean and true. Chapter 3’s Example 16 — Arithmetic and concatenation applies the same distinction to a supplier price.
The empty notes. payload.notes is "". Make it print "no notes". Run default first, watch it not work, then fix it.
Show answer
Example 309 — Distinguish empty text from null.
Input payload — order.json:
{
"orderId": "A-1001",
"customer": "Dana",
"status": "shipped",
"coupon": null,
"notes": "",
"tags": ["gift", null, "rush"],
"total": 32,
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
]
}
%dw 2.0
output application/json
---
{
withDefault: payload.notes default "no notes",
withIsEmpty: if (isEmpty(payload.notes)) "no notes" else payload.notes
}
{
"withDefault": "",
"withIsEmpty": "no notes"
}
default only replaces null, and "" is not null. isEmpty is true for null and for the empty string, array and object, which is what “no notes” means here.
A helper of your own on top of the module. Import everything from Orders, add a script-level discounted(order, pct) that applies a percentage to the subtotal, and print each order’s total after a 10% discount and tax. Run it with --path.
Show answer
Example 310 — Combine module and local helpers.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import * from Orders
fun discounted(order, pct) = subtotal(order) * (1 - pct)
---
payload map (order) -> { orderId: order.orderId, total: withTax(discounted(order, 0.10)) }
[
{
"orderId": "A-1001",
"total": 31.104
},
{
"orderId": "A-1008",
"total": 11.178
}
]
subtotal and withTax come from the module and discounted from the header, and the body cannot tell them apart. 32 * 0.9 * 1.08 is 31.104, printed with every digit; a "0.00" format schema from chapter 17 is how a receipt would round it.
Two copies, two aliases. With Orders.dwl at the root and a second copy under modules/, import both under different aliases and call each one’s withTax(100). Run it.
Show answer
Example 311 — Alias two module paths.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import Orders as Flat
import modules::Orders as Nested
---
{ flat: Flat::withTax(100), nested: Nested::withTax(100) }
{
"flat": 108,
"nested": 108
}
Two modules with the same base name coexist because the alias, not the file name, is what the script refers to. The same trick lets a script hold two versions of a module side by side during a migration.
Predict the winner. Wildcard-import Orders and then Pricing, call withTax(100), and write down the answer before you run it. Then make the script print both rates without renaming anything in either module.
Show answer
108: the first import owns the name. Swapping the lines gives 120. To get both:
Example 312 — Qualify colliding module functions.
Use orders.json as payload, as above.
%dw 2.0
output application/json
import Orders
import Pricing
---
{ orders: Orders::withTax(100), pricing: Pricing::withTax(100) }
{
"orders": 108,
"pricing": 120
}
Qualified names resolve each call to its module, and no wildcard was needed.
Defaults, then a split. Apply the defaults { currency: "USD", giftWrap: false, coupon: "NONE" } to the order header (everything except items and tags), then partition the lines into bulk (quantity four or more) and single, and total the bulk revenue. Run it. What happened to the coupon default, and why?
Show answer
Example 313 — Merge defaults and partition order lines.
Use order.json as payload, as above.
%dw 2.0
import mergeWith from dw::core::Objects
import sumBy, partition from dw::core::Arrays
output application/json
var defaults = { currency: "USD", giftWrap: false, coupon: "NONE" }
---
do {
var merged = defaults mergeWith (payload - "items" - "tags")
var split = payload.items partition (i) -> i.qty >= 4
---
{
order: merged,
bulk: split.success.sku,
single: split.failure.sku,
bulkRevenue: split.success sumBy (i) -> i.price * i.qty
}
}
{
"order": {
"currency": "USD",
"giftWrap": false,
"orderId": "A-1001",
"customer": "Dana",
"coupon": null
},
"bulk": [
"PEN-01",
"CLP-08"
],
"single": [
"PAD-22"
],
"bulkRevenue": 20
}
The coupon is null, not "NONE". The payload has coupon: null as a real key, and a present key wins the merge even when its value is null. Strip the nulls from the override with filterObject (v) -> v != null before merging, or default the field.
Validate a script that imports a module. Run dw validate on the transform that imports orderTotal from orders::OrderMath. What does it report, and what is the check you should run instead?
Show answer
Compiling `01_import_name`...
[ERROR] Unable to resolve module with identifier orders::OrderMath.
2| import orderTotal from orders::OrderMath
^^^^^^^^^^^^^^^^^
Location:
01_import_name (line: 2, column:24)
1 errors found
validate has no --path, so it cannot find project modules and reports every one as unresolved. The check is dw run -s -i payload=order.json --path=. -f …, which compiles the imports for real and exits 255 if anything is wrong.
Header scope and declaration order
A function body and a variable initializer do not have identical forward-reference rules. These small probes distinguish them, then show a local binding that shadows a header name.
Example 314 — Reject a variable forward reference.
Input payload — order.json:
{
"orderId": "A-1001",
"customer": "Dana",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
]
}
%dw 2.0
output application/json
var total = sum(lineTotals)
var lineTotals = payload.items map ($.price * $.qty)
---
{ total: total }
[ERROR] Error while executing the script:
[ERROR] There is no variable named 'lineTotals'
3| var total = sum(lineTotals)
^^^^^^^^^^
Trace:
at 314-reject-a-variable-forward-reference::main (line: 3, column: 17) at:
3| var total = sum(lineTotals)
^^^^^^^^^^
A variable initializer cannot directly select a later variable binding. This failure is a declaration-order rule, not an instruction to make bindings mutable.
Example 315 — Call a function that reads a later binding.
Input payload — order.json:
{
"orderId": "A-1001",
"customer": "Dana",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
]
}
%dw 2.0
output application/json
var totals = payload.items map lineTotal($)
fun lineTotal(item) = item.price * item.qty
---
totals
[
10,
12,
10
]
The function can refer to a later header declaration. This does not make all variable forward references legal: compare the preceding failure. Declaring constants before the helpers that use them keeps the distinction out of ordinary calling code.
Example 316 — Keep an inner rate local.
Input payload — orders.json:
[
{ "orderId": "A-1001", "customer": "Dana",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
] },
{ "orderId": "A-1008", "customer": "Femi",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 1 },
{ "sku": "INK-03", "price": 3.0, "qty": 3 }
] }
]
%dw 2.0
output application/json
var taxRate = 0.08
fun withTax(amount) = do {
var taxRate = 0.20
---
amount * (1 + taxRate)
}
---
{ inDo: withTax(100), inHeader: 100 * (1 + taxRate) }
{
"inDo": 120,
"inHeader": 108
}
The inner taxRate is visible inside the do block and leaves the header binding unchanged. Both names are immutable; the different results come from two scopes. Distinct names usually communicate this exception better than shadowing.
Inspect a value
Use these probes when a source field looks right but reaches an operation with an unexpected type.
Example 317 — Inspect model value types.
Input payload — order.json:
{
"orderId": "A-1001",
"customer": "Dana",
"items": [
{ "sku": "PEN-01", "price": 2.5, "qty": 4 },
{ "sku": "PAD-22", "price": 6.0, "qty": 2 },
{ "sku": "CLP-08", "price": 1.0, "qty": 10 }
]
}
%dw 2.0
output application/json
---
{
whole: typeOf(payload),
id: typeOf(payload.orderId),
items: typeOf(payload.items),
price: typeOf(payload.items[0].price),
qty: typeOf(payload.items[0].qty),
missing: typeOf(payload.coupon),
aKey: typeOf(keysOf(payload)[0]),
date: typeOf(|2026-09-13|),
dateTime: typeOf(|2026-09-13T09:15:00Z|),
localDateTime: typeOf(|2026-09-13T09:15:00|),
time: typeOf(|09:15:00Z|),
localTime: typeOf(|09:15:00|),
period: typeOf(|P2D|),
zone: typeOf(|+02:00|)
}
{
"whole": "Object",
"id": "String",
"items": "Array",
"price": "Number",
"qty": "Number",
"missing": "Null",
"aKey": "Key",
"date": "Date",
"dateTime": "DateTime",
"localDateTime": "LocalDateTime",
"time": "Time",
"localTime": "LocalTime",
"period": "Period",
"zone": "TimeZone"
}
typeOf returns a Type value, whose name the JSON writer prints as text. Any is the common supertype. The temporal literals differ in the information they retain, as chapter 22 explained. A selected key can also carry metadata, which is why Key is distinct from String.
Example 318 — Inspect the same fields in a text feed.
Input payload — order_from_feed.json:
{
"orderId": "A-1001",
"customer": "Dana",
"placed": "13/09/2026",
"items": [
{ "sku": "PEN-01", "price": "2.50", "qty": "4" },
{ "sku": "PAD-22", "price": "6.00", "qty": "2" },
{ "sku": "CLP-08", "price": "1.00", "qty": "10" }
]
}
%dw 2.0
output application/json
---
{
price: typeOf(payload.items[0].price),
qty: typeOf(payload.items[0].qty),
placed: typeOf(payload.placed)
}
{
"price": "String",
"qty": "String",
"placed": "String"
}
The field names alone do not establish their types. This feed supplies price, quantity and date as strings. The conversions in chapters 3 and 21 establish the values that arithmetic and date operations need.
Comments