Avoid Loops with XPath

Iterating over an array of values to perform complex actions is easy with actions such as Apply to Each or Do Until within Power Automate.

But what if you simply want to concatenate some strings together? Or sum some numbers? Or do some other simplistic x, y, or z logic?

I already know about loops, take me to the goods! (tl;dr)

Here’s an example of a loop to sum an array of 15 numbers: [1,2,3,4,5,6,7,8,9,10,0.1,0.2,0.3,0.4,0.5]

4 seconds to sum 15 numbers??? Not good.

As you can see from the image above, even for relative simple operations, these looping actions can actually be quite expensive performance-wise for your flow. Four seconds or more to sum 15 numbers? Yikes. (I’ve even seen it take 6+ seconds, not good.)

Plus, loops can be hard to troubleshoot when things go wrong. How frustrating is it when you can’t expand the loop action or any of its children to see what the loop is currently doing while it’s running?

Shucks, we can’t expand child condition

That’s unfortunate, we can’t see what the loop is currently doing.

So, do I really need a loop?

Maybe, maybe not.

Going back to our inefficient number sum example above. Let’s think about how we can approach this a different way.

Here’s what we know:

  1. We have an array of values (numbers)
  2. We need a single output (sum)
  3. We don’t want to use a loop

Since we are dealing with an array (aka collection), the first thing to do is to see which actions and/or expressions we have that can target them.

Under the Data Operation category of the Power Automate actions list, there are some possible action candidates: Filter, Select, and Join which all deal with arrays. Although only one returns a single value, Join.

Join still is not what we need as it simply concatenates values together into a single string. It doesn’t allow us to do any math operations, like we need for summing numbers.

Next, let’s look at the expressions we have available in the dynamic content panel. Some possible candidates are: concat, union, join, add.

Ooh … add, but shucks it only takes 2 parameters, and array has 15 values. That won’t work.

Wait … here’s an interesting expression: xpath(...). What’s XPath?

XPath is an expression language that navigates and selects nodes in an XML document.

This sounds very similar to what a loop can do. Alright, we may be on to something here! Can XPath sum values? Why yes it can!

Expressions to the rescue!

Using XPath against our array requires us to convert it XML. This is easy with the xml(...) expression.

Unfortunately, arrays don’t directly convert to XML, so we need to massage it slightly.

XML expects a single element at the root of the document, and arrays are multiple elements. So we can simply wrap our array in a JSON object to accomplish this.

Assuming our array of numbers is in a compose action like this:

Our array of 15 numbers

Here’s the expression to wrap our number array:

json(concat('{"r":{"v":', outputs('Numbers'), '}}')))

Which produces this new object:

{
  "r": {
    "v": [1,2,3,4,5,6,7,8,9,10,0.1,0.2,0.3,0.4,0.5]
  }
}

With this new JSON object, “r” is now the root property, and it looks like this when we convert it to XML:

<r><v>1</v><v>2</v><v>3</v><v>4</v><v>5</v><v>6</v><v>7</v><v>8</v><v>9</v><v>10</v><v>0.1</v><v>0.2</v><v>0.3</v><v>0.4</v><v>0.5</v></r>

Now the XPath needed to sum the values looks like this:

sum(//v/text()[number(.) = number(.)])

Here’s what it is doing:

  1. sum(...) – this function adds element values together (our numbers),
  2. //v/text() – this selects all of the elements, the “v” nodes (our numbers), and returns their text value,
  3. number(.) = number(.) – this logic basically means “only when the value, converted to a number, equals a number” or something like that.

So, the complete XPath sum expression looks like this:

xpath(xml(json(concat('{"r":{"v":', outputs('Numbers'), '}}'))), 'sum(//v/text()[number(.) = number(.)])')

Here’s the output of the run of this flow action:

I feel the need, the need for speed!

Zero seconds! That’s better.

And if you really want to see the time differences, here are the metrics, using the start/end times of the actions:

Not too shabby XPath!

Tip to calculate how long an action took to execute (in seconds), use this expression:

div(sub(ticks(actions('XPath_Sum')?['endTime']),ticks(actions('XPath_Sum')?['startTime'])),float(10000000))

Takeaway

As you can see, using XPath is blazingly fast compared to the loop timing.

And as you increase the number of items, the loop tends to get slower, but XPath generally does not.

For example, summing a range of numbers 1 to 100: range(1, 100)

31 seconds to sum 1 to 100 … boo!
Still zero seconds!

Loops aren’t always necessary and can be a performance hog when used unnecessarily.

I encourage you, spend a little forethought on what your flow will do, and you can have it do mighty things, simpler and faster.

See my next post on how to generate cleaner concatenated strings using XPath.

Happy coding!


Follow My Blog

Get new content delivered directly to your inbox.

Author: Joey

I develop modern solutions leveraging the Microsoft cloud technologies: Power Platform and SharePoint Online, with a focus on process automation. I got my start writing code professionally beginning with Microsoft VB6, but really fell in love with code (and the art of it) with Microsoft .NET Framework version 1.0. I'm also a fan of Superman, so don't be surprised if references creep in. Up, Up, and Away!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: