DataWeave's update Function
Learn how to effectively use DataWeave's update function, as well as how to work around common gotchas.
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 fromdw::util::Values
- The
update
function is paired with another function,with
. You do not need to importwith
. - 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:
- Use selectors to isolate the Object you want to update
- Transform the Object as needed (likely using
mapObject
to identify which index of the Object you want to modify) - 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.