Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Add a 'default' operator #370

Open
jhorbulyk opened this issue Sep 30, 2019 · 12 comments
Open

Proposal: Add a 'default' operator #370

jhorbulyk opened this issue Sep 30, 2019 · 12 comments

Comments

@jhorbulyk
Copy link
Contributor

In JavaScript, it is possible to write statements such as:
value = input || 0;
where a variable or result can be assigned the result of another variable if it is assigned and otherwise it is assigned a default value. The closest thing in JSONata that can do this is:
$value := input ? input : 0;
which is significantly more verbose. Ideally, it would be nice if there was a way to do this in JSONata. One possibilty is to add a new operator (e.g. ||) or is to tweak the existing or operator.

@markmelville
Copy link
Contributor

markmelville commented Nov 19, 2019

This would be a wonderful addition to Jsonata. || is the correct JavaScript idiom, but other options could be considered, such as ?: or ??, as borrowed from other languages.

To expound on the OPs example of how verbose workaround can get, if you have a large expression, you don't want to have to repeat it in a ternary:

$lookup($lookup($collections,$collectionName),$key) ? $lookup($lookup($collections,$collectionName),$key) : 'fallback'

the most terse way to do it now is with statements:

($value:=$lookup($lookup($collections,$collectionName),$key); $value ? $value : 'fallback')

with a new operator it could be:

$lookup($lookup($collections,$collectionName),$key) || 'fallback'

Another workaround is a method, but an operator would be better to short-circuit the potentially expensive work of resolving the second operand:

$coalesce($value,$expensiveFallback())

@andrew-coleman
Copy link
Member

Just want to clarify desired behaviour here. The expression:
$input ? $input : "fallback"
will evaluate the condition by casting the value of $input to a Boolean as if by applying the $boolean() function. If it evaluates to true, then the conditional expression will return the value of $input, otherwise it will return the string "fallback". So, if $input contains the empty sequence, empty array, empty object, empty string, zero or null, then the expression will return "fallback". Is that the behaviour you are looking for with this 'default' operator?

Alternatively, you might want to default to a fallback value only if the $input is non-existent (empty sequence). In this case you can take advantage of sequence flattening to do the 'coalesce'. For example:
[$input, $input2, 'fallback'][0]
will return the first value in the flattened sequence. If $input has a value, then it is returned, otherwise if $input2 has a value, it is returned. Otherwise "fallback" is returned. This is a common idiom in XPath (>=2.0) and is the equivalent of the SQL coalesce function.

@jhorbulyk
Copy link
Contributor Author

The expression:
$input ? $input : "fallback"
will evaluate the condition by casting the value of $input to a Boolean as if by applying the $boolean() function. If it evaluates to true, then the conditional expression will return the value of $input, otherwise it will return the string "fallback". So, if $input contains the empty sequence, empty array, empty object, empty string, zero or null, then the expression will return "fallback". Is that the behaviour you are looking for with this 'default' operator?

That's a good question. The behavior of JavaScript's || operator does behave that way. However, I could also argue that applying the $exists() function to the $input argument, we may be building a more useful tool.

In this case you can take advantage of sequence flattening to do the 'coalesce'.

That is a cool trick that I didn't know.

@timkindberg
Copy link

I think the sequence flattening would confuse ppl not familiar with that pattern. I'd prefer the || and && patterns and I tried those first out of instinct but they didn't work.

@jmrnilsson
Copy link

bump

@jasonmp85
Copy link

Lmao how does this still not exist

@markmelville
Copy link
Contributor

Lmao how does this still not exist

My take is that one of the project owners showed some ways to achieve this, and though it's not what we might want, it does get the job done. Our options are to use it, or submit a pull request.

@markmelville
Copy link
Contributor

Look what you made me do. I created a pull request. In the past I tried adding an operator and everything was blowing up. Perhaps it's different ever since the 2.0 change from generators to async.

@jmrnilsson
Copy link

As a work around I use reduce function that accumulates first !null from a list. But it doesn’t short-circuit, so it isn’t great but offer better readability than nested ternary operators.

@xanderificnl
Copy link

In one use case, I was returned a Cannot set property of non-object type: flow.outlet which I tried to mitigate via || "" to no avail.

Concatenating to an empty string got me the desired result, that is:

"" & $flowContext("devices." & payload.device)

It's definitely not going to win any beauty contests.

@jmrnilsson
Copy link

I came across the solution I think. But it’s undocumented. The weirdest thing is, using half of a ternary operator works. So something, ‘$maybe ? “default value”’ would work. Found this when kept chasing the latter part of ternary.

@evbo
Copy link

evbo commented Feb 6, 2025

There is another way to default a value:

$lookup($merge([{'foo': 'default'}, {'foo': some_var}]), 'foo')

The problem with ternary is if you have syntax occurring before it - it gets interpreted as part of the condition whereas lookup and merge bound the input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants