Skill 116 · AWS Step Functions
Subchapter 116.7
references/transforming-data.mdMarkdown6 KBView on GitHub
JSONata expressions are written inside {% %} delimiters in string values:
"Output": "{% $states.input.customer.name %}"
"TimeoutSeconds": "{% $timeout %}"
"Condition": "{% $states.input.age >= 18 %}"Rules:
{% (no leading spaces) and end with %} (no trailing spaces).Type and Resource must be constant strings.{% %} is treated as a literal value.'text'), not double quotes. The expression is already inside a JSON double-quoted string, so double quotes would break the JSON.( expr1; expr2; ...; finalExpr ) where semicolons separate sequential expressions and the last expression is the return value.$ or $$ at the top level.$parse() instead $eval for deserializing JSON strings."Output": "{% 'Hello ' & $states.input.name %}"
"Condition": "{% $states.input.status = 'active' %}"Never use double quotes inside the expression:
❌ "Output": "{% "Hello" %}"
✓ "Output": "{% 'Hello' %}"Use := inside ( ... ) blocks to bind intermediate values within a single JSONata expression. Semicolons separate each binding, and the last expression is the return value:
"Output": "{% ( $subtotal := $sum($states.input.items.price); $tax := $subtotal * 0.1; $discount := $exists($couponValue) ? $couponValue : 0; {'subtotal': $subtotal, 'tax': $tax, 'discount': $discount, 'total': $subtotal + $tax - $discount} ) %}"You can also define local helper functions:
"Assign": {
"summary": "{% ( $formatPrice := function($amt) { '$' & $formatNumber($amt, '#,##0.00') }; $subtotal := $sum($states.input.items.price); {'itemCount': $count($states.input.items), 'subtotal': $formatPrice($subtotal), 'total': $formatPrice($subtotal * 1.1)} ) %}"
}Local variables bound with := exist only within the ( ... ) block. They do not affect state machine variables. To persist values across states, use the Assign field.
"Output": {
"expensiveItems": "{% $states.input.items[price > 100] %}"
}"Output": {
"total": "{% $sum($states.input.items.price) %}",
"average": "{% $average($states.input.items.price) %}",
"count": "{% $count($states.input.items) %}"
}"Output": {
"fullName": "{% $states.input.firstName & ' ' & $states.input.lastName %}",
"upper": "{% $uppercase($states.input.name) %}",
"trimmed": "{% $trim($states.input.rawInput) %}"
}"Output": "{% $merge([$states.input, {'processedAt': $now(), 'status': 'complete'}]) %}"Use $reduce to transform an array into a key-value object:
"Assign": {
"priceByProduct": "{% $reduce($states.input.items, function($acc, $item) { $merge([$acc, {$item.productId: $item.price}]) }, {}) %}"
}Given [{"productId": "A1", "price": 10}, {"productId": "B2", "price": 25}], this produces {"A1": 10, "B2": 25}.
Use $lookup to access an object property by a variable key:
"Output": {
"price": "{% $lookup($priceByProduct, $states.input.productId) %}"
}This is essential when you’ve built a mapping object with $reduce and need to retrieve values dynamically. Standard dot notation ($priceByProduct.someKey) only works with literal key names.
"Output": {
"tier": "{% $states.input.total > 1000 ? 'gold' : 'standard' %}",
"discount": "{% $exists($states.input.coupon) ? 0.1 : 0 %}"
}Test if a value exists in an array with in:
"Condition": "{% $states.input.status in ['pending', 'processing', 'shipped'] %}"Concatenate arrays with $append:
"Assign": {
"allIds": "{% $append($states.input.orderIds, $states.input.returnIds) %}"
}"Output": {
"names": "{% $states.input.users.(firstName & ' ' & lastName) %}"
}"Assign": {
"requestId": "{% $uuid() %}",
"randomValue": "{% $random() %}"
}"Assign": {
"batches": "{% $partition($states.input.items, 10) %}"
}"Assign": {
"parsed": "{% $parse($states.input.jsonString) %}"
}"Assign": {
"hash": "{% $hash($states.input.content, 'SHA-256') %}"
}JSONata timestamps are strings, so you can’t compare them directly with < or >. Use $toMillis to convert to numeric milliseconds:
"Condition": "{% $toMillis($states.input.orderDate) > $toMillis($states.input.cutoffDate) %}"Useful for sorting timestamps, calculating durations, or finding the most recent entry:
"Assign": {
"ageMinutes": "{% $round(($toMillis($now()) - $toMillis($states.input.createdAt)) / 60000, 2) %}",
"mostRecent": "{% $sort($states.input.timestamps, function($a, $b) { $toMillis($a) < $toMillis($b) })[0] %}"
}| Function | Purpose |
|---|---|
$partition(array, size) | Partition array into chunks |
$range(start, end, step) | Generate array of values |
$hash(data, algorithm) | Calculate hash (MD5, SHA-1, SHA-256, SHA-384, SHA-512) |
$random([seed]) | Random number 0 ≤ n < 1, optional seed |
$uuid() | Generate v4 UUID |
$parse(jsonString) | Deserialize JSON string |