A compliance audit found SSN values 4 levels deep in our API responses last year. One recursive function masks everything. Then null values in production crashed 400 responses.
TL;DR
- Recursive
maskPIIfunction dispatches on type: Object → check fields, Array → recurse, Primitive → pass through - Works at any nesting depth with one function call:
maskPII(payload) - Null values crash it — add explicit null handling before the Object case
- No hardcoded paths needed — the function finds PII fields at any level
The Problem: PII at Unknown Depth
Our org chart API returns hierarchical data:
{
"company": "Acme Corp",
"ceo": {
"name": "Alice Chen", "ssn": "123-45-6789", "email": "alice@acme.com",
"reports": [
{
"name": "Bob Martinez", "ssn": "234-56-7890", "email": "bob@acme.com",
"reports": [
{"name": "Carol Nguyen", "ssn": "345-67-8901"}
]
}
]
}
}
SSN at level 1 (CEO), level 2 (VP), level 3 (Director). The compliance requirement: mask ALL of them. The depth varies per org — some go 6 levels.
The Recursive Solution
%dw 2.0
output application/json
fun maskSsn(s: String): String = "***-**-" ++ s[-4 to -1]
fun maskEmail(e: String): String = do { var parts = e splitBy "@" --- parts[0][0] ++ "****@" ++ parts[1] }
fun maskPII(data: Any): Any =
data match {
case obj is Object -> obj mapObject (value, key) ->
if ((key as String) == "ssn") {(key): maskSsn(value as String)}
else if ((key as String) == "email") {(key): maskEmail(value as String)}
else {(key): maskPII(value)}
case arr is Array -> arr map maskPII($)
else -> data
}
---
maskPII(payload)
Type dispatch: Objects get field-level checking. Arrays recurse into each element. Strings, numbers, booleans pass through unchanged.
One call — maskPII(payload) — handles any depth.
100 production-ready DataWeave patterns with tests: mulesoft-cookbook on GitHub
The Null Trap
Production payloads had null values at level 3 — a manager with no email. The data match block dispatched null to... somewhere unexpected. It tried mapObject on null. Crash. 400 API responses failed.
The fix: add explicit null handling.
fun maskPII(data: Any): Any =
data match {
case is Null -> null
case obj is Object -> obj mapObject ...
case arr is Array -> arr map maskPII($)
else -> data
}
case is Null -> null catches null before it reaches the Object handler. Now null passes through unchanged.
Testing Recursive Functions
I test with these edge cases now:
- Empty object
{}at each level - Empty array
[]at each level -
nullat each level - Mixed types in arrays:
["text", 42, null, {"key": "value"}] - Maximum expected depth (6 levels for our org chart)
5 minutes of setup in the DataWeave Playground. Prevented every recursive bug since.
100 patterns with MUnit tests: github.com/shakarbisetty/mulesoft-cookbook
60-second video walkthroughs: youtube.com/@SanThaParv
Top comments (0)