JSONata evaluate expression inside function node is returning empty object at debug window

Hi,

I can work with JSONATA expression with inject node and change node fine but I'm facing an issue while working with JSONata inside the Function node.

When I use JSONATA's .evaluate() method, i get empty object at the debug window. Here’s the code snippet I’m using:

let temp = [ ]
temp[0] = jsonata(`$length(${arr[0]})`).evaluate();
temp[1] = jsonata(`$length(${arr[1]})`).evaluate();
temp[2] = jsonata(`$length(${arr[2]})`).evaluate();
temp[3] = jsonata(`$length(${arr[3]})`).evaluate();
temp[4] = jsonata(`$length(${arr[4]})`).evaluate();

msg.payload = temp;
return msg;

Here is the o/p i'm getting

Interestingly, when I use .ast() instead of .evaluate(), it produces the expected output: Here is the expression i'm using for .ast() method:**

let arr = ["He", "is", "a", "good", "boy"]


let temp = [ ]
temp[0] = jsonata(`$length(${arr[0]})`).ast();
temp[1] = jsonata(`$length(${arr[1]})`).ast();
temp[2] = jsonata(`$length(${arr[2]})`).ast();
temp[3] = jsonata(`$length(${arr[3]})`).ast();
temp[4] = jsonata(`$length(${arr[4]})`).ast();


msg.payload = temp;
return msg;

// ouput i'm getting:

Could someone help me understand why evaluate() is returning an empty result, while ast() works correctly? Am I missing something in the way JSONata is being processed inside the Function node?

Any insights or suggestions would be greatly appreciated!

Regards,
Shubham

Hi,

ast(..) returns the abstract syntax tree which is the internal representation of the expression passed to the jsonata(...) function the abstract syntax tree is used by the parser to evaluate the expression.

let arr = ["He", "is", "a", "good", "boy"]


let temp = [ ]
temp[0] = jsonata(`$length(${arr[0]})`).ast();

because you're using backticks (`), the code is evaluate to $length(He) which for JSONata makes no sense since "He" isn't a variable name or anything else. If you want the length of a string, then you need to add quotes:

temp[0] = jsonata(`$length("${arr[0]}")`).evaluate()

that would evaluate to $length("He") which would JSONata would evalaute to 2.

Just to note that node-red has some api modules to use JSONata in a function node.

let arr = ["He", "is", "a", "good", "boy"]
let expr = RED.util.prepareJSONataExpression("$$.$length($)", node);
 RED.util.evaluateJSONataExpression(expr, arr, function(err, output) {
        if (err) {
            node.warn(JSON.stringify(err));
        }
           msg.payload = output; 
        });
return msg;

@gregorius

Sorry, missed that part. However, result is still the same. I cannot make it work.

I mentioned .ast() to showing that one method produces some result, while other returns empty object

Did you supply the data that you wish the expression to use. ...evaluate(arr) May need to be a property of an object

No, it doesn't make a difference.

Interestingly, after your reply, I looked into the JSONata documentation to find information on providing arguments inside evaluate().
[Embedding and Extending JSONata · JSONata]

There, they use await in front of the evaluate() expression.

I added await, and it worked. However, using .evaluate(arr) does not make a difference, meaning both of the following produce the same result. But I don't know why?

So, as of know we have two ways of getting our answer.

  1. Method 1 -> Using node-red util method that you mentioned above.

  2. Method 2 -> Using await infront of evaluate expression

let arr = ["He", "is", "a", "good", "boy"]

let temp = []

temp[0] = await jsonata(`$length("${arr[0]}")`).evaluate();
temp[1] = await jsonata(`$length("${arr[1]}")`).evaluate();
temp[2] = await jsonata(`$length("${arr[2]}")`).evaluate();
temp[3] = await jsonata(`$length("${arr[3]}")`).evaluate();
temp[4] = await jsonata(`$length("${arr[4]}")`).evaluate();

msg.payload = temp;

return msg;

Or this may work, untested.

let arr = ["He", "is", "a", "good", "boy"];
msg.payload  = await jsonata("$$.$length($)").evaluate(arr);

Yes, its working as well.

Can i have an explanation of second line of your code?

$$ - the base context of data supplied e.g. arr
.length($) - map through the base context (which is an array) and apply $length to each index value e.g $ ( the values of the map loop context)

$$ - base array
.length($) - iterates over each element of base array returns length

Thanks for your support.

Just clarify the base context is an arr as the supplied data arr is an array. if you supply an object then the base context would be an object, thus a slightly different expression would be required.

Yeah, I'm trying to work on it. Though i got answer using long method, short method is taking me some time. Let me give it some more tries

This is shortest method i came up with for Objects

const example = { "a": 4, "b": 4.4, "c": 4.8, "d": -4.4 };
msg.payload = await jsonata(`$each($$, function($v){$floor($v)})`).evaluate(example);
return msg;

Output i got [4,4,4,-5]

But i cannot able in this format { "a": 4, "b": 4, "c": 4, "d": -5 }. Can you give me some kind of hint? Though long method gave us the desired result.

I did tried creating a array of keys and array of values seperately and used zip function, but that will create array of array

This is how my short method look like:

const example = { "a": 4, "b": 4.4, "c": 4.8, "d": -4.4 };
const arr_of_obj = await jsonata(`$each($$, function($v, $k){{$k : $floor($v)}})`).evaluate(example);
msg.payload = await jsonata("$merge($$)").evaluate(arr_of_obj);
return msg;

Are you doing it in same manner?

Why don't you just use javascript?

Object.keys(msg.payload).forEach(key => {
    msg.payload[key] = Math.floor(msg.payload[key])
});
1 Like
$merge(
    $each($$, function($v,$k){
        {$k: $floor($v)}
    })
)

or

(
$props := $keys($$);
    $$.*#$i.${$props[$i]:$floor($)}
)

@Colin.

My Goal was to get proficient on working with JSONATA expression. Though your approach is good if i plan to execute using JS.

OK, understood.

Ok, at last we got the desired for object using $each and $merge but at the cost of readability.

@gregorius , @E1cid , @Colin .

Thank you all.