Altinn / Altinn/app-frontend-react

Expressions: List and object support

Open
#1,179 8 comments 0 reactions 0 assignees View on GitHub
area/logic kind/feature-request org/ssb squad/data status/for-consideration
Dominant language
TypeScript
Stars
21
Forks
33
Avg merge
6d 23h
Merged PRs (30d)
3

Description

Right now, the expression language does not work with lists/arrays or objects, and will simply return `null` if you try to look one up from the data model. This is unfortunate, and the language should support these values - but we need to provide tools to manipulate them as well.

Needed functionality:

- [x] `list` function
- Right now, all expressions are JSON arrays. If we want to support lists/arrays in the expression language, we need a way to represent a static list of values. Say, for example, that you wanted to check if a data model path is in a list of values with `["in", ["dataModel", ...], ["value1", "value2", "value3"]]`, the expression engine would think `["value1", "value2", "value3"]` is another expression - and halt when the function `value1` is not found.
- So, to mitigate this problem, we could create a `list` function that simply takes any amount of parameters and returns them as a list/array. So the above could be written as `["in", ["dataModel", ...], ["list", "value1", "value2", "value3"]]`.
- [ ] `listLength` function (or similar - decide in tandem with the string length function name in Altinn/app-frontend-react#1176)
- An obvious function to include is one to measure the length of a list (given as the first argument).
- Lookup a `group` and `list` binding
- A `group` binding binds to a list/array of objects. If we support both data types in the expression language, we should support looking up these values using the `component` function. I think the most obvious thing here is to extend `component` to work on any binding type (if there is only one binding), and allow you to specify which binding (such as `address`) as a second parameter if the component has multiple bindings.
- [ ] `objKeys` function (or similar)
- A function that returns a list of keys inside an object, simply `Object.keys()`
- [ ] `pluck` function
- This function should be the powerhouse of the object support. It should pluck a value from an object (given a key). If given a list as a parameter, it should pluck out the key from every _object in the list_ and return a list.
- Example: `["pluck", "name", [{"name": "Ole Martin"}, {"name": "Per"}]]` => `["Ole Martin", "Per"]`
- We should create robust tests for this so we don't end up supporting side-effects like looking up the length of a list of lists (because `list.length` is valid in javascript)
- [ ] `atIndex` function
- This function is for lists what `pluck` is for objects. It is named differently to indicate they are not interchangeable, but they operate the same. If you `["atIndex", 1, ["list", 1, 2, 3]]` you should get `2` back.
- We should create robust tests for this so we don't end up supporting side-effects like picking characters from a string.
- [ ] `first` function
- First conceived in https://github.com/Altinn/app-frontend-react/issues/1669#issuecomment-2583156849
- Similar to `atIndex`, but in cases where you don't know the index. This way you can use an expression to find the first element in a list that matches another expression.
- If this uses the `value` function for sub-expressions, it would ruin cases where you would like to use both the `value` from the current context (such as in `optionFilter`) and this sub-expression to evaluate each list item. For that reason we might want to use something else, such as an `item` function that behaves much like `value`.

Other simple functions (moved from Altinn/app-frontend-react#1176):
- [ ] `sum`
- Should take any amount of numbers as input, and also lists of numbers. All these numbers should be summed, and the resulting number should be returned. `null` and non-numeric values should be considered to be `0` (i.e. no effect on the sum).
- [ ] `average`
- Same as above, but should produce the average of all the numbers summed. `null` and non-numeric values should be considered entries when calculating the average, as we should make a filtering function for those cases where invalid values should not be considered instead.
- [ ] `in`
- Shortcut function similar to `equals`, but compares the first argument against multiple others to check if the first argument matches either one of them. Should support lists of items as parameters, and should then check inside the lists as well.
- [ ] `notIn`
- Synonym for `["not", ["in", ....]]`

Other functions we should consider:
- `map` (see `argv` in Altinn/altinn-studio#16764)
- As an example, you could `["map", ["upperCase", ["argv", 0]], ["list", "foo", "bar"]]` to get `["FOO", "BAR"]`
- This would require deeper expression engine support than what is implemented for functions right now, as this is similar to a closure/callback data type.
- `filter`
- `reduce`
- `sort` (for lists of strings/numbers)
- `reverse` (for lists of strings/numbers)
- `concat` support for joining lists?

Contributor guide

Open the contributing guide

Research direction

The issue does not name implementation files, tests, or a specific entry point. Start by locating the expression engine and its existing function implementations, then review the linked discussions for naming decisions. Done would require a narrowed scope, defined behavior for the requested list/object functions, and robust tests for supported inputs and rejected side effects.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.