Beginner questions on IF expressions and wildcard usage in function node

Hello everyone,
I am trying to step by step build a small MQTT filter flow.

I have my MQTT input all set up and can receive messages and I can "filter" using a function node with e.g.

var msg = { payload: msg.payload.ENERGY['Total'] };
return msg;

First issue :slight_smile:
However, if a payload does not have the key 'Total' I get the following error message

TypeError: Cannot read properties of undefined (reading 'Total')

So I tried ensuring that the key exists, but it is also throws an error.
Tried

if ('Total' in msg.payload.ENERGY) {
    var msg = { payload: msg.payload.ENERGY['Total'] };
    return msg;
}

and

if (msg.payload.hasOwnProperty('Total')) {
    var msg = { payload: msg.payload.ENERGY['Total'] };
    return msg;
}

Second issue :slight_smile:
I would also like to be able to use wildcards, but my syntax seems wrong and I cannot find the solution.
Here I am trying to find all payloads that contain a key with "ATC" in its name

payload: msg.payload+'.[ATC*]'

Also tried # and +instead of * (because those can be used e.g. when subscribing to an MQTT topic).

Thank you for your support :slight_smile:
Alex

EDIT1:
Also unsuccessfully tried

if (msg.payload.ENERGY['Power'] !== undefined) {
    var msg = { payload: msg.payload.ENERGY['Power'] };
    return msg;
}

because apparently this is faster than the "in" or "hasOwnProperty" approach ( arrays - Checking if a key exists in a JavaScript object? - Stack Overflow )

EDIT2:
I think I may have solved my first issue:

EDIT3:
And @Colin solved the second issue :raised_hands:

Try this...

ENERGY total should appear in msg.payload in the next part of your flow.

Oops - just re-read your original post - I doubt if the above is wwhat you want (sorry).

Thank you Dave,
but I think your approach is essentially what I initially did and which does produce the correct output IF that key exists.
I am trying to prevent errors by first checking if the key exists and only then creating the new msg.

Umm - this is what I would have tried (just like your attempt).
Not near a PC so can't try the code at the moment

if ('Total' in msg.payload.ENERGY) {
  console.log('Total exists in msg.payload.ENERGY');
} else {
  console.log('Total does not exist in msg.payload.ENERGY');
}

Sorry - this was meant for you not me.

Unfortunately same as with my approach in first post. :frowning:

TypeError: Cannot use 'in' operator to search for 'Total' in undefined

Okay, I got a step closer.
I cannot check for the key directly but first need to ensure that the object is actually present.

if (msg.payload.ENERGY !== undefined) {
    var msg = { payload: msg.payload.ENERGY['Power'] };
    return msg;
}

EDIT:
And in a second step then check the key.

This seems to work:

if (msg.payload.ENERGY !== undefined) {
    if (msg.payload.ENERGY['Power'] !== undefined) {
        var msg = { payload: msg.payload.ENERGY['Power'] };
        return msg;
}
}

Now I need to figure out the wildcard problem.

For the record, you don't need to use ['Power']. You only need to do that if the property name (Power) has special characters in it. So you can use msg.payload.ENERGY.Power.

I am using the Total in a second step to ensure that I do not get an error if the object ENERGY exists but does not have a key named Total.
Or do you mean that this is redundant because the error is only linked to missing object and not to missing keys?

Sorry, I had multiple typos in my post, now corrected. I just meant that wherever you use ['Power'] you can use .Power

1 Like

For the wildcard problem I think I need to use regular expressions, but I seem to be doing it wrong.

if (msg.payload./ATC/ !== undefined) {
    var msg = { payload: msg.payload./ATC/ };
    return msg;
}

This should be all payloads that contain an object starting with "ATC", but I get a syntax error.

Does the payload contain multiple properties or will it only ever contain one, such as ATC123?
Also is the ATC always at the start?

The payload can contain all kinds of properties (if I am using the word 'properties' correctly).
Placement could theoretically vary but it is usually not at the start.

Here is an example payload:

{"Time":"2023-09-03T14:48:50","ATC0d5c0c":{"mac":"a4c1380d5c0c","Temperature":21.6,"Humidity":66.2,"DewPoint":15,"Btn":0,"Battery":37,"RSSI":-68},"ATC8f1b4f":{"mac":"a4c1388f1b4f","Temperature":21.9,"Humidity":62.9,"DewPoint":14.5,"Btn":0,"Battery":69,"RSSI":-62},"TempUnit":"C"}

Here is a function which detects if msg.payload has an element whose key begins with "ATC".
I don't know how efficient it is!

for (let key in msg.payload) {
    node.warn (key)
    if (/^ATC/.test(key)) {
        node.warn (key + " begins with ATC")
        node.warn ("It's value is " + msg.payload[key])
    }
}
   
return msg;

In that case you need to test all the properties (or keys) to see if any of them match.

I didn't mean if it is the first property, I meant if then name starts with ATC, or if the ATC could be anywhere in the property name.

Assuming that it is always at the start then you can iterate through the properties (or keys) using something like

Object.keys(msg.payload).forEach((element) => {
  if (element.startsWith("ATC")) {
    msg.payload = msg.payload[element]
    node.send(msg)
  }
})
1 Like

Your code works, thank you!

But I am a bit surprised that I have to test all keys one by one/looping through each. Why is that?

When I use

if (msg.payload.ENERGY['Power'] !== undefined) {

this also checks if any of the keys matches 'Power'. Would the same approach not be possible by ending the msg.payload.ENERGY with a regular expression for "starts with ATC"?
msg.payload.ENERGY + regular expression for "starts with ATC"

EDIT1:
So, this did not work

if (msg.payload.startsWith("ATC") !== undefined) {
    var msg = { payload: msg.payload.startsWith("ATC") };
    return msg;
}

TypeError: msg.payload.startsWith is not a function

Firstly, you mixed dot with bracket notation. Secondly, you cannot use wildcards in object or array selectors.

What you can do is to use a filter. Please do some reading on the filter function in JavaScript.


Array.prototype.filter() - JavaScript | MDN (mozilla.org)

Also note that filter is an Array function. If your payload is an object rather than an array, you will need to convert using Object.keys. That gets you an array of the keys that you can filter and pass into a loop. Alternatively, if the key is also represented in the objects values, you can use Object.values and filter more directly.

1 Like

Follow up question.
The output of this is e.g.

{"mac":"a4c1380d5c0c","Temperature":21.6,"Humidity":64.7,"DewPoint":14.7,"Btn":0,"Battery":37,"RSSI":-64}

but there is also a payload undefined.
So the filtering is not quite working correctly yet.

MQTT node is set to output parsed JSON object. So it is always a parsed JSON object that is being processed.

I was unsuccessful with the filter function also.

var msg = { payload: msg.payload.filter(function (str) { return str.includes('ATC'); }) };
return msg;

Gives me error

TypeError: msg.payload.filter is not a function

But msg.payload is my array, is it not?

Show us the full function please.

It is worth finding why it does not work, but @TotallyInformation is correct, using filter would probably be better.

It doesn't check if any of the keys match, it just goes to the one called Power and tries to get it. Only then does it realise that it does not exist.

It is like the difference between phoning extension 123 and seeing if anyone replies, rather than asking if any of the 12x numbers reply. In the first case (extension 123 or key Power) you just try it, for the second (12x or ATX???) the only way to know is to try them all.