VOOZH about

URL: https://dev.to/thasha/recursive-pii-masking-in-dataweave-one-function-for-any-depth-and-the-null-trap-3dka

⇱ Recursive PII Masking in DataWeave: One Function for Any Depth (and the Null Trap) - DEV Community


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 maskPII function 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:

  1. Empty object {} at each level
  2. Empty array [] at each level
  3. null at each level
  4. Mixed types in arrays: ["text", 42, null, {"key": "value"}]
  5. 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