jsonata-js / jsonata

JSONata query and transformation language - http://jsonata.org
MIT License
2.05k stars 220 forks source link

Proposal: Add a 'default' operator #370

Open jhorbulyk opened 5 years ago

jhorbulyk commented 5 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 2 years ago

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 commented 1 year ago

bump

jasonmp85 commented 1 year ago

Lmao how does this still not exist

markmelville commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 10 months ago

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.