Use the Index Luke!

To fetch the value of a certain field in a nested data structure, you might want to use the simple dot-notation.

ri!records.address.street

The issue is, that when there is no address, this breaks.

The solution is, to use the index() or property() function. Both work in the exact same way and the only difference is that the word “index” indicates using a numerical index to fetch an item from a list, while “property” indicates fetching the value of a field by name. Use a normal string when navigating in dictionaries or maps, and the record-syntax for record data.

And, before I forget that, the solution is, index() and property() allow you to provide a default value in case that operation fails.

So, how do you use one of these functions to fetch that street?

property(
  property(ri!records, "address", null),
  "street",
  null
)

In case there is no address or street, the returned value is null.

But the code looks ugly, especially when there are more levels.

There is an edge case in index() and property() that allows you to provide a list of fields.

property(ri!records, "address", "street", null)

But that is not supported and makes the syntax checker unhappy.

In the following, I would like to present my solution to this problem.

My Solution

At its core, diving down into a nested data structure is an iterative algorithm. In Appian, I can use to reduce() function to implement that. It iterates on a list of fields, dives down one step for each item, passing the returned structure to the next iteration.

reduce(
  index(_,_,null),
  ri!records,
  {"address", "street"}
)

Before reading further, make sure to understand what is going on. Read the documentation about the reduce() function, and spend a bit of time doing your own research.

I am waiting for you …

… cool! Let’s continue.

Reusable Expression

To turn this into a powerful reusable expression, I added the following features:

  • Support for dot-notation
  • Support for mixed numeric and named indexing
  • Support for record-syntax

Find the code snippet below. All the rule inputs are of type Any.

if(
  a!isNullOrEmpty(ri!path),
  ri!data,
  reduce(
    index(_,_,ri!default),
    ri!data,
    a!match(
      value: typeof(ri!path),
      
      /* Format like "fieldParent.3.fieldChild" */
      whenTrue: fv!value = 'type!{http://www.appian.com/ae/types/2009}Text',
      then: a!forEach(
        items: split(ri!path, "."),
        expression: if(
          exact(tostring(tointeger(fv!item)), fv!item),
          tointeger(fv!item),
          fv!item
        )
      ),
      
      /* Format like {"fieldParent", 3, "fieldChild"} */
      whenTrue: count(ri!path) > 1,
      then: ri!path,
      
      /* Others, probably record fields. Must be a list to calm the reduce function. */
      default: {ri!path}
    )
  )
)

Summary

Add this to your toolbox to avoid issues when navigating in nested data structures.

I am curious about your feedback and ideas.

Keep on rocking!

7 thoughts on “Use the Index Luke!

  1. Nice one Stefan, this simplifies the code nicely, and less chance of missed parenthesis with all the nested property or index functions.

    1. property() takes only 1 value as index, while index() can take multiple values as index.
      property() takes only text as index, index() can take any type as index.

      1. Only as per the documentation. And the fact that index() can take multiple indexes (passing a list as value is fine, multiple parameters are not) is undocumented and has ugly edge cases. Try this code snippet:
        {
        property({“a”, “b”, “c”}, 2),
        index({“a”, “b”, “c”}, 2),
        property({a: “hello”}, “a”),
        index({a: “hello”}, “a”),
        }

  2. What do you think the downside of this way index(ri!records, “address”, “street”, null).
    I deal with a lot of nested json in this way
    I havent found a problem till with the approach. Am I missing something ?

Leave a Reply