How to Iterate over XML Attributes with DataWeave

Jose Luis Clua
Another Integration Blog
4 min readNov 22, 2023

Recently, I had a hard time figuring out how to implement a DataWeave transformation to iterate over a set of attributes within an XML element. I could not find a clear resource explaining how to achieve this, so I thought my solution might make for an interesting topic for a technical article.

The Problem

Consider the XML document below which represents a collection of books to be sold in a bookstore. Each book element within the XML has a child element, price, which contains a set of attributes we need to iterate over.

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price currency="USD" tax="0.15" date="2023-10-29">29.99</price>
</book>
<book category="WEB">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<year>2003</year>
<price currency="CAD" tax="0.1" date="2023-10-29">49.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price currency="AUD" tax="0" date="2023-10-29" shipping="2.00">39.95</price>
</book>
</bookstore>

Let’s imagine we have these requirements regarding the price element’s attributes:

  • Currency: We need to replace the currency code with the currency description (e.g., USD would become US Dollars).
  • Tax: We want to replace the decimal value with the text “not included” when it is not equal to 0 and with the text “included” when it is equal to 0.
  • Date: We want to remove this attribute.
  • Other Attributes: Any other attribute present in the price element should be displayed when they exist. In our example, the shipping attribute is present just for one book.

With these requirements in mind, the desired output should look something like this:

<?xml version='1.0' encoding='UTF-8'?>
<bookstore>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price currency="US Dollars" tax="not included">29.99</price>
</book>
<book category="WEB">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<year>2003</year>
<price currency="Canadian Dollars" tax="not included">49.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price currency="Australian Dollars" tax="included" shipping="2.00">39.95</price>
</book>
</bookstore>

The Solution

The first step is to understand how DataWeave’s mapObject() function works and how we can isolate the price element for all others. Assuming we know that price is a leaf node, we can construct a recursive function to traverse all XML nodes:

fun formatBooks(value, key) =
if (value is Object)
(key): value mapObject(v, k, i) ->
formatBooks(v, k)
else
key match {
case "price" ->
formatPrice(value, key) // To be defined later
else ->
(key): value
}

As the XML nodes are traversed, if the current node is an object, then it calls itself, but if it is a leaf node, it looks for the price element so we can transform it. Now that we have isolated the price formatting logic within the formatBooks() function, we need to understand how to iterate over the price element’s attributes (currency, tax, and date) and then apply the required logic for each one.

The key to solving this problem is to understand how to create a reference to all attributes. This may be accomplished with the following code:

(key) @((key.@)): value

The key.@ portion of the above expression returns back all of the attributes contained within an XML element (referred to here by key) as an Object of name/value pairs which we may then iterate over by using DataWeave’s mapObject() function. I leveraged the key.@ operation within my formatPrice() function to isolate the price element’s attributes. I created another function, formatPriceAttrs(), which routes the processing of each attribute by using a match/case scope. The else clause permits the output of any other attribute which is present in the XML element and does not require any special handling, such as the shipping attribute in our example.

fun formatPrice(priceValue, priceKey) =
(priceKey) @((formatPriceAttrs(priceKey.@))): priceValue

fun formatPriceAttrs(priceAttributesObject) =
priceAttributesObject mapObject (v, k, i) ->
k match {
case "currency" ->
(k): currencyLookup(v) // to be defined later
case "tax" ->
(k): taxDescription(v) // to be defined later
case "date" ->
{} // {} will exclude the attribute
else ->
(k): v // all others pass through as-is
}

What follows is the complete DataWeave code, which you may copy into an instance of DataWeave Playground and play around with:

%dw 2.0
output application/xml

fun currencyLookup(curCode) =
curCode match {
case "USD" -> "US Dollars"
case "AUD" -> "Australian Dollars"
case "CAD" -> "Canadian Dollars"
else -> "Unknown"
}

fun taxDescription(rate) =
if (rate ~= 0) // ~= enables a String value to be compared as a Number
"included"
else
"not included"

fun formatBooks(value, key) =
if (value is Object)
(key): value mapObject(v, k, i) ->
formatBooks(v, k)
else
key match {
case "price" ->
formatPrice(value, key)
else ->
(key): value
}

fun formatPrice(priceValue, priceKey) =
(priceKey) @((formatPriceAttrs(priceKey.@))): priceValue

fun formatPriceAttrs(priceAttributesObject) =
priceAttributesObject mapObject (v, k, i) ->
k match {
case "currency" ->
(k): currencyLookup(v)
case "tax" ->
(k): taxDescription(v)
case "date" ->
{}
else ->
(k): v
}
---
payload mapObject (value, key, index) ->
formatBooks(value, key)

Well, there you have it! I hope this short technical article will be helpful to others who may be unsure as to how to get DataWeave to iterate over attributes within XML elements.

--

--