DataWeave Deep Dive: Transforming Complex JSON Structures
DataWeave is MuleSoft’s functional transformation language — and once you move past the basics, it opens up a remarkably expressive toolkit for tackling real-world integration complexity. This post covers patterns I’ve used extensively in production healthcare projects.
The Problem with Nested Data
API integrations rarely deal with flat data. A typical healthcare payload might look like this:
{
"patient": {
"id": "P-001",
"demographics": { "name": "John Doe", "dob": "1985-03-12" },
"encounters": [
{ "id": "E-001", "type": "inpatient", "diagnoses": ["Z00.00", "J06.9"] },
{ "id": "E-002", "type": "outpatient", "diagnoses": ["K21.0"] }
]
}
}
Flattening this into a canonical format requires nested map, conditional logic, and careful null-handling.
Pattern 1: Recursive Key Lookup
One of my most-used DataWeave utilities is a function that locates a key anywhere inside a deeply nested object — without knowing its exact path:
%dw 2.0
output application/json
fun findKey(obj, key) =
if (obj is Object)
(obj[key] default null)
then obj[key]
else (obj pluck ((v) -> findKey(v, key))
filter ($ != null))[0] default null
else if (obj is Array)
(obj map findKey($, key) filter ($ != null))[0] default null
else null
---
{ result: findKey(payload, "dob") }
I wrote this library during the OSB-to-MuleSoft migration — saving days of hardcoded path expressions across dozens of transformations.
Pattern 2: Dynamic Key Generation
Sometimes the output schema is data-driven — field names come from a metadata table rather than being hardcoded:
%dw 2.0
output application/json
var fieldMap = {
"PTNT_ID": "patientId",
"PTNT_NM": "patientName",
"ENCTC_DT": "encounterDate"
}
---
payload map (row) -> (
row mapObject ((value, key) ->
{ (fieldMap[key as String] default (key as String)): value }
)
)
This pattern was central to our metadata-driven Oracle-to-Snowflake sync API — users could add new field mappings without touching transformation code.
Pattern 3: Error-Safe Transformation
Production transformations need to handle incomplete data gracefully:
%dw 2.0
output application/json
fun safeDate(raw) =
try(() -> raw as Date {format: "yyyy-MM-dd"})
match {
case is Success -> $.result
else -> null
}
fun safeString(val) =
if (val == null or val == "" or val == "NULL") null
else val as String
---
payload.patients map (p) -> {
id: p.id,
name: safeString(p.name),
dob: safeDate(p.dateOfBirth)
}
Pattern 4: Composing Reusable Functions
DataWeave modules let you extract utilities into a shared library, importable across projects:
// modules/StringUtils.dwl
%dw 2.0
fun toTitleCase(s: String): String =
s splitBy " "
map (word) -> (upper(word[0]) ++ lower(word[1 to -1]))
joinBy " "
fun slugify(s: String): String =
lower(s) replace /[^a-z0-9]+/ with "-"
// main transformation
%dw 2.0
import toTitleCase, slugify from modules::StringUtils
output application/json
---
{
displayName: toTitleCase(payload.name),
urlSlug: slugify(payload.name)
}
Key Takeaways
- Write utility functions early — even a simple
safeStringwrapper pays dividends across a large project - Use
try()for external data — never trust that an incoming date or number is well-formed - Metadata-driven transforms are your best friend when schemas evolve frequently
- Test with MUnit — DataWeave logic is fully unit-testable without spinning up a full Mule runtime
If you’re working with complex integrations and want to go deeper, the DataWeave playground is the fastest way to experiment.