Down the Rabbit Hole

If you're interested in the "why" behind the update function, read on! If you're only interested in knowing how to use it, skip to "The update Function"

Do you remember the first time you were using Mule 4 and you needed to update just a single value in your payload? If you were lucky, you had a flat Object that you needed to update:

{
  "one"   : 1,
  "two"   : 2,
  "three" : "whoops"
}

Although it's admittedly a little cumbersome, it's easy enough to change that "whoops" into a 3:

payload mapObject (v,k,i) -> 
  if (k ~= "three") 
    {(k): 3} 
  else 
    {(k): v}

It's cumbersome because not only do you need to identify which value you'd like to update, you also need to explictly define what to do with the values you dont want to update. You have to do this because all values in DataWeave are immutable. You cannot modify a value in-place like you can with Python (e.g. obj["three"] = 3)

If your payload is an Array of flat Objects, its a bit more cumbersome, but still easily accomplished. You just map over the Array and use mapObject to do the updates:

payload map ($ mapObject (v,k,i) ->
  if (k ~= "three") 
    {(k): 3} 
  else 
    {(k): v}
)

What if your payload is more complex? What if you needed to mask a password that was nested deep in the data structure like this:

{
  "one" : 1,
  "two" : 2,
  "three" : {
    "one" : 1,
    "two" : 2,
    "three" : {
      "password" : "mask me!"
    }
  }
}

How would you approach this problem?

You could take the brute-force approach:

payload mapObject (v,k,i) ->
  if (k ~= "three")
    {(k): v mapObject (v,k,i) ->
      if (k ~= "three")
        {(k): v mapObject (v,k,i) -> 
          if (k ~= "password")
            {(k): "*****"}
          else {(k): v}
        }
      else {(k): v}
    }
  else {(k): v}

It's not very elegant, is it? It's not easy to understand the purpose of the code, either. You can't reuse it because the keys (i.e., "three", "password") are hardcoded. You can't use it on other data structures because this function wouldn't know what to do if it was passed an Array. Aside from the fact that this solution works, it leaves a lot to be desired.

Instead of a brute-force approach, you might have read a previous post of mine, recognized this pattern, and implemented the following solution:

fun applyWhenKey(e, fn, predicate) =
  e match {
    case is Array  -> $ map applyWhenKey($, fn, predicate)
    case is Object -> $ mapObject ((v, k) -> 
                        if (predicate(k))
                          {(k): fn(v)} 
                        else
                          {(k): applyWhenKey(v, fn, predicate)})
    else -> $
  }
---
applyWhenKey(
  payload, 
  ((v) -> "*****"),
  ((k) -> k ~= "password")
)

This is a significant improvement over the brute-force approach. You now have a reusable function that's easy to understand if you have a good grasp on recursion.

Let's modify the previous example just slightly:

{
  "one" : 1,
  "two" : 2,
  "three" : {
    "one" : 1,
    "two" : 2,
    "three" : {
      "password" : "mask me!"
    },
    "password" : "don't worry about me!"
  }
}

Now you have one password that you should mask, and another one that you should leave alone. If you try to use the applyWhenKey function, you get this:

{
  "one" : 1,
  "two" : 2,
  "three" : {
    "one" : 1,
    "two" : 2,
    "three" : {
      "password" : "*****"
    },
    "password" : "*****"
  }
}

An elegant solution to this problem lies in the update function.

The update Function

I find the update function is pretty inuitive, and easier to demonstrate than to explain in-depth. Recall our first example:

{
  "one"   : 1,
  "two"   : 2,
  "three" : "whoops"
}

Here's how we would change "whoops" to 3 using the update function:

import update from dw::util::Values
---
payload update "three" with 3

Simple, right? This reads easily as, "update payload.three with the value 3," or, "update the payload at key 'three' with the value 3." How about our moderatly complex password example:

{
  "one" : 1,
  "two" : 2,
  "three" : {
    "one" : 1,
    "two" : 2,
    "three" : {
      "password" : "mask me!"
    }
  }
}

Here's how you would mask that password with the update function:

import update from dw::util::Values
---
payload update ["three", "three", "password"] with "*****"

You could read this as "update payload.three.three.password with *****."

Finally, how about the situation that applyWhenKey could not handle?

{
  "one" : 1,
  "two" : 2,
  "three" : {
    "one" : 1,
    "two" : 2,
    "three" : {
      "password" : "mask me!"
    },
    "password" : "don't worry about me!"
  }
}

Since we can target exactly what we want to change, the solution is the same as our previous example:

import update from dw::util::Values
---
payload update ["three", "three", "password"] with "*****"

The update function gets you away from using recursion or brute-force solutions to solve these problems.

Recap

Here's a quick recap:

  • To use the update function, you must import it from dw::util::Values
  • The update function is paired with another function, with. You do not need to import with.
  • When working with Objects, identify what to update by using Strings. If you need to update an Object more than one level deep, use an Array of Strings.

Working with Arrays

Updating Arrays is simple as well. Instead of using Strings, you use integers to identify the index of the Array you'd like to update:

[1,2,4] update 2 with 3
> Returns: [1,2,3]

You deal with nested Arrays in the same way you deal with nested Objects, by using an Array to identify what you'd like to update:

[[1,2],[3,4],[6,6]] update [2,0] with 5
> Returns: [[1,2],[3,4],[5,6]]

When Objects and Arrays are in the same data structure, you use Strings and integers as needed to identify what you'd like to update:

{
  "one"  : 1,
  "nums" : [1,2,4]
} update ["nums", 2] with 3
> Returns: {"one": 1, "nums": [1,2,3]}

There's one other piece of data we need to learn how to update: attributes.

Working with Attributes

To identify attributes you'd like to update, you use Strings. Since Strings are already used to update Objects, we need to wrap the String in a helper function, attr, so that the update function knows to look for an attribute and not just an Object key. The attr function needs to be imported from dw::util::Values as well. Here an example payload:

<root>
  <numbers>
    <number parity="even">1</number>
  </numbers>  
</root>

And here's how you'd update the "parity" attribute:

import attr, update from dw::util::Values
---
payload update ["root", "numbers", "number", attr("parity")] with "odd"

Updating Objects with Repeated Keys

While the update function is easy to use, there is one complication to consider: updating Objects with repeated keys. You're most likely to come across this edge-case when reading or writing XML. As an example, let's say you wanted to update the following payload so that the "parity" attribute for the number 1 is odd, and you want to leave the number 2 as-is:

<root>
  <numbers>
    <number parity="even">1</number>
    <number parity="even">2</number>
  </numbers>  
</root>

You might try to do so like this:

%dw 2.0
output application/xml

import attr, update from dw::util::Values
---
payload update ["root", "numbers", "number", attr("parity")] with "odd"

But it would return this:

<root>
  <numbers>
    <number parity="odd">1</number>
    <number parity="odd">2</number>
  </numbers>  
</root>

When you run into this situation, you can do the following:

  1. Use selectors to isolate the Object you want to update
  2. Transform the Object as needed (likely using mapObject to identify which index of the Object you want to modify)
  3. Use update to insert the updated Object back into the data structure.

Here's how you'd accomplish this with our previous example:

var updatedNumbers = payload.root.numbers              // 1)
                       mapObject (v,k,i) ->            // 2)
                         if (i == 0)
                           {(k) @(parity: "odd"): v}
                         else
                           {(k): v}
---
payload update ["root", "numbers"] with updatedNumbers // 3)

Now the update code will return the desired payload:

<root>
  <numbers>
    <number parity="odd">1</number>
    <number parity="even">2</number>
  </numbers>
</root>

Conclusion

The update function provides a clean and intuitive interface for updating single values on nested collections. You can easily update values in Array, Objects, as well as attributes. You can find the offical documentation for the update function here. If you have any questions, feel free to leave a comment below, or reach out to me on LinkedIn.