Grouping Records in DataWeave with groupBy
groupBy is useful when a flat array needs to be organized into groups before the next integration step.
Typical examples include grouping records by country, account, status, business unit, or another routing key.
The important shape change is:
Array → groupBy → Object of Arrays
That output shape is worth understanding because groupBy behaves differently from functions such as map and filter.
Basic Example
Suppose the payload contains orders from multiple countries:
[
{ "orderId": "O1", "country": "US", "total": 100 },
{ "orderId": "O2", "country": "CA", "total": 200 },
{ "orderId": "O3", "country": "US", "total": 300 }
]
Group them by country:
%dw 2.0
output application/json
---
payload groupBy (order) -> order.country
The result is conceptually:
{
"US": [
{ "orderId": "O1", "country": "US", "total": 100 },
{ "orderId": "O3", "country": "US", "total": 300 }
],
"CA": [
{ "orderId": "O2", "country": "CA", "total": 200 }
]
}
Each distinct grouping value becomes a key whose value contains the matching records.
Grouping Salesforce Records
Suppose a collection of Salesforce records contains AccountId:
[
{ "Id": "C1", "AccountId": "A1", "Name": "Ravi" },
{ "Id": "C2", "AccountId": "A2", "Name": "Maria" },
{ "Id": "C3", "AccountId": "A1", "Name": "James" }
]
Group contacts by account:
payload groupBy (contact) -> contact.AccountId
This can be useful when downstream processing happens once per parent account rather than once per individual contact.
Handling Missing Grouping Keys
If the grouping field can be null or missing, decide what that means explicitly.
For example:
payload groupBy (record) -> record.region default "UNASSIGNED"
Now records without a region are placed in a known group.
Whether that is correct depends on the integration. In another design, missing region may be a validation error and should not be grouped at all.
Grouping Before Aggregation
groupBy is often the first step in an aggregation.
Suppose you want order totals by country. First group the records, then transform each group:
%dw 2.0
output application/json
---
(payload groupBy (order) -> order.country)
mapObject (orders, country) -> {
(country): {
orderCount: sizeOf(orders),
totalAmount: sum(orders.total)
}
}
For the earlier example, the output is:
{
"US": {
"orderCount": 2,
"totalAmount": 400
},
"CA": {
"orderCount": 1,
"totalAmount": 200
}
}
Notice the combination:
groupBy → creates an object of grouped arrays
mapObject → transforms those groups
This is a good example of choosing DataWeave functions based on data shape.
Converting Groups Back into an Array
A downstream API may expect an array rather than a dynamic-key object.
You can transform the grouped object into an array of group summaries:
%dw 2.0
output application/json
---
(payload groupBy (order) -> order.country)
pluck (orders, country) -> {
country: country,
orderCount: sizeOf(orders),
orders: orders
}
Conceptual output:
[
{
"country": "US",
"orderCount": 2,
"orders": [ ... ]
},
{
"country": "CA",
"orderCount": 1,
"orders": [ ... ]
}
]
pluck is useful here because it turns object entries into array items while giving access to both each group value and its key.
Grouping for Routing or Batching
Grouping is not only for reporting.
Imagine records must be sent to different downstream endpoints based on business unit:
payload groupBy (record) -> record.businessUnit
The groups can then become units of subsequent processing.
Similarly, records might be grouped by a parent identifier before constructing one request per parent.
However, do not confuse logical grouping with transport-level batch sizing. A group containing 50,000 records may still need to be divided into smaller chunks before calling a downstream system.
Grouping by a Derived Value
The grouping key does not have to come directly from a field.
For example:
payload groupBy (order) ->
if ((order.total default 0) >= 1000)
"HIGH_VALUE"
else
"STANDARD"
This produces business-defined groups based on a rule rather than a source attribute.
If that rule is reused or complex, move it into a named function for readability.
Normalize Keys Before Grouping When Appropriate
Source data sometimes contains inconsistent values such as:
US
us
Us
If those values are intended to represent one group, normalize first:
payload groupBy (record) -> upper(record.country default "UNKNOWN")
But normalization should reflect a known business rule. Do not merge distinct source values merely because they look similar.
groupBy vs filter
These functions answer different questions.
filter asks:
Which records should remain?
groupBy asks:
How should the remaining records be organized?
For example:
(payload filter (order) -> order.status == "READY")
groupBy (order) -> order.region
This first selects eligible orders and then organizes them by region.
Quick Decision Guide
| Requirement | Typical function |
|---|---|
| Keep selected records | filter |
| Transform records | map |
| Organize records by a key | groupBy |
| Transform grouped object entries | mapObject |
| Turn grouped object entries into an array | pluck |
Practical Rule
Use groupBy when the next processing step naturally thinks in terms of collections sharing the same key.
Then pay attention to the resulting shape: you no longer have a simple array. You have an object whose values are arrays, and the next DataWeave function should be chosen accordingly.