Boolean Function

Hi, I am trying to find the simplest approach to code the following. I have a Boolean function statement checking the state of multiple variables:

if (a > 2 && b >7 && c <5) { }

and for each variable I have a Boolean flag (saved in context from dashboard input) indicating whether the that variable should be included in the function, so if:

include_a = true; include_b = false; include_c = true

the statement needs to be:

if (a > 2 && c <5) { }

Seems that there must be a more elegant solution than what I can come up, which a long list of list statements check for all variations (there are lot more variable than 3). Thanks!

I hesitate to call it elegant but this function builds up the expression and then evals it,
It expects the variables to arrive as an object with keys "a", "b", etc.
Testing has been limited to the two inject strings and one flow variable shown!

Untitled 1

Edit: simplified code

[{"id":"19154000c821042e","type":"inject","z":"7124eaac06216d5b","name":"flow.useb = true","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":160,"y":40,"wires":[["89606ab29439ffea"]]},{"id":"e1b39d51c46e251f","type":"inject","z":"7124eaac06216d5b","name":"flow.useb = false","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"false","payloadType":"bool","x":160,"y":80,"wires":[["89606ab29439ffea"]]},{"id":"89606ab29439ffea","type":"change","z":"7124eaac06216d5b","name":"","rules":[{"t":"set","p":"useb","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":60,"wires":[[]]},{"id":"52ad7e216485bdc4","type":"function","z":"7124eaac06216d5b","name":"JiggeryPokery","func":"const reference = { // ALL possible variables\n    \"a\": { \"testvalue\": 2, \"operator\": \">\", \"flowVar\":\"usea\"},\n    \"b\": { \"testvalue\": 7, \"operator\": \">\", \"flowVar\":\"useb\"},\n    \"c\": { \"testvalue\": 5, \"operator\": \"<\", \"flowVar\":\"usec\"},\n    \"d\": { \"testvalue\": 19, \"operator\": \"<\", \"flowVar\": \"duntexist\" }, // flowVar need not exist\n    }\nlet cmd = \"(\"\nfor (const key in reference) {\n     if (msg.payload.hasOwnProperty(key)) {\n        const usethisproperty = flow.get(reference[key].flowVar) ?? true \n        if (usethisproperty) { \n            cmd += msg.payload[key] + reference[key].operator + reference[key].testvalue + \" && \"\n        }  \n    }\n}\ncmd += ' 1==1)'\nnode.warn(cmd)\nlet result = eval(cmd)\nnode.warn(result)","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":200,"wires":[[]]},{"id":"21f8da981884b87e","type":"inject","z":"7124eaac06216d5b","name":"{\"a\": 3, \"b\": 8, \"c\": 4}","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"a\": 3, \"b\": 8, \"c\": 4}","payloadType":"json","x":150,"y":200,"wires":[["52ad7e216485bdc4"]]},{"id":"94ba0372e2937a84","type":"inject","z":"7124eaac06216d5b","name":"{\"a\": 3, \"b\": 6, \"c\": 4}","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"a\": 3, \"b\": 6, \"c\": 4}","payloadType":"json","x":150,"y":240,"wires":[["52ad7e216485bdc4"]]}]

Hi, thank you! Yes, seems to work - just need to understand it. Also, noticed 3 warnings in the function (line 10, 11, 18) including "eval can be harmful" (which I've never seen before).

What is the reason for having this in the end cmd += ' 1==1)' ? is it because otherwise the expression ends with && ?

const reference = { // ALL possible variables
    "a": { "testvalue": 2, "operator": ">", "flowVar":"usea"},
    "b": { "testvalue": 7, "operator": ">", "flowVar":"useb"},
    "c": { "testvalue": 5, "operator": "<", "flowVar":"usec"},
    "d": { "testvalue": 19, "operator": "<", "flowVar": "duntexist" }, // flowVar need not exist
    }
let cmd = "("
for (const key in reference) {
     if (msg.payload.hasOwnProperty(key)) {
        const usethisproperty = flow.get(reference[key].flowVar) ?? true 
        if (usethisproperty) { 
            cmd += msg.payload[key] + reference[key].operator + reference[key].testvalue + " && "
        }  
    }
}
cmd += ' 1==1)'
node.warn(cmd)
let result = eval(cmd)
node.warn(result)

Yes, appending 1==1 is because there would otherwise be a floating &&.

eval can be harmful - Yes, the function effectively executes whatever code you feed it with.

An example, I have a global context variable "model".
If I inject this payload {"a":"node.warn(global.get('model'))"} the node outputs this

There seems to be some "expected" syntax warnings line 10 and 11 yet it still works ... do you see them as well?

My flag variables are global localfile store global.get ('a', 'file') - where do I specify the file in flow.get(reference[key].flowVar)

No, I don't know where you see these warnings - in Monaco editor?

I don't understand the question, but it's early in the morning and pre coffee.

You have the line: const usethisproperty = flow.get(reference[key].flowVar) - which gets the flow variables? Mine are global saved in file ... so I need to change the line to const usethisproperty = global.get(reference[key].flowVar) ... just not sure about the syntax to specify 'file'

Don't remember which editor (the older version), here are the warnings

line 10
expected identifier instead saw "?"
expected ":" insteas saw true

line 10
expected identifier instead saw "if"
expected an operator instead saw "("
expected an operator instead saw ")"
missing semicolon

I don't use filesystem context storage so I'm not sure how it works. I should learn about it.

The first warning is because the ?? true syntax requires node.js 14 or above (I think).
If you have an older version use || true

I have given up semicolons for New Year. So far my code is a tiny bit slimmer. Add them if you like. :grinning:

ps You can protect the function code from malicious/accidental javascript injection by changing the for loop like this

for (const key in reference) {
    if (msg.payload.hasOwnProperty(key)) {
        const num = msg.payload[key]
        if (typeof(num) == "number") {
           const usethisproperty = flow.get(reference[key].flowVar) ?? true 
           if (usethisproperty) { 
               cmd += num + reference[key].operator + reference[key].testvalue + " && "
           }  
        }
    }
}

pps The square bracket notation is needed because the "key" in "reference.key.flowVar" would be interpreted as a literal not a variable.
If your global context variable is "a" and you normally access it by global.get ('a', 'file'), I assume that global.get(reference.a.flowVar, 'file') would work.
In the function that would be global.get(reference[key].flowVar, 'file')

Indeed! This actually took care of all warnings - including semicolons :slight_smile:

As far as filesystem context storage there is a small adjustment in the settings file and the code becomes: flow.set ('variable', value, 'file') and flow.get ('variable', 'file'). So I am trying to figure out if I simply need to change your line to flow.get(reference[key].flowVar, 'file') ?

Jus tried that, something is not working. Also, one of my variables is string, is this correct?

    "A": { "testvalue": "SB", "operator": "===", "flowVar":"use_A"},

Sorry, stuck on a simple thing. What would this look like without injection node, just values defined in function and I can set the flag variables individually (no need to put them in the loop)

let a = "SB"
let b = 2
let c = 9

let use_a = false
let use_b = true
let use c = true

const reference = { 
    "a": { "testvalue": "SB", "operator": "===", "flowVar":"use_A"},
    "b": { "testvalue": 7, "operator": ">", "flowVar":"useb"},
    "c": { "testvalue": 5, "operator": "<", "flowVar":"usec"},
    "d": { "testvalue": 19, "operator": "<", "flowVar": "duntexist" }, // flowVar need not exist
    }
let cmd = "("

for (const key in reference) {
    if (msg.payload.hasOwnProperty(key)) {
        const num = msg.payload[key]
        if (typeof(num) == "number") {
            const usethisproperty = flow.get(reference[key].flowVar, 'file') || true 
           if (usethisproperty) { 
               cmd += num + reference[key].operator + reference[key].testvalue + " && "
           }  
        }
    }
}

cmd += '1==1)'
node.warn(cmd)
let result = eval(cmd)
node.warn(result)

Specification creep!

If some of your data can be a string, you can't sanitise it with

const num = msg.payload[key]
if (typeof(num) == "number") {
...
}

or it will never test the value of A.

I see; didn't occur that there would be a difference in code, should have said

if (a > 2 && b >7 && a == "abc") { }

the good news is that const usethisproperty = global.get(reference[key].flowVar, 'file') is working

I guess I can convert that string to number. The only thing I still can't figure out is how to specify the
a: "testvalue" in the function as just a = 200 rather than the msg.payload format.

let x = global.get('ex', 'file')
let y = "2"

const reference = { 
    "a": { "testvalue": x, "operator": "==", "flowVar":"iSm5"},
    "b": { "testvalue":  y, "operator": ">", "flowVar":"pr"}
    }
let cmd = "("

for (const key in reference) {
    if (msg.payload.hasOwnProperty(key)) {
        const num = msg.payload[key]
        if (typeof(num) == "number") {
            const usethisproperty = global.get(reference[key].flowVar, 'file')  
           if (usethisproperty) { 
               cmd += num + reference[key].operator + reference[key].testvalue + " && "
           }  
        }
    }
}

cmd += '1==1)'
node.warn(cmd)
let result = eval(cmd)
node.warn(result)

Special handling for strings, I also tested it works with boolean (c):

const reference = { // ALL possible variables
    "a": { "testvalue": "abc", "operator": "===", "flowVar":"usea"},
    "b": { "testvalue": 7, "operator": ">", "flowVar":"useb"},
    "c": { "testvalue": false, "operator": "===", "flowVar":"usec"},
    "d": { "testvalue": 19, "operator": "<", "flowVar": "duntexist" }, // flowVar need not exist
    }
let cmd = "("
for (const key in reference) {
    if (msg.payload.hasOwnProperty(key)) {
        const usethisproperty = flow.get(reference[key].flowVar) ?? true 
        if (usethisproperty) { 
            let testvalue = reference[key].testvalue
            if (typeof (testvalue) == "string") {     // if this is a string, wrap it in single quotes
                testvalue = "'" + testvalue + "'"
                msg.payload[key] = "'" + msg.payload[key] + "'"
            }
           cmd += msg.payload[key] + reference[key].operator + testvalue + " && "
        }
    }
}
cmd += ' 1==1)'
node.warn(cmd)
let result = eval(cmd)
node.warn(result)

Correct. And Node.js v14 LTS should be the minimum version you should be using now since v12 is out of support.

Good for you! I also dropped them a long time back. I use a slightly modified (read less-annoying) version of JavaScript "Standard" for my formatting these days. That also doesn't use ;.

It is the same as the default memory storage but it copies the data to a file every nn seconds (20 or 30 I think but you can adjust it). Of course, it also has to serialise the data so some things you can keep in memory storage will change when saved to file.

I would add a test to make sure that the operator value is a valid operator, that would avoid the danger of accidentally sending something damaging.

I actually got Node.js version v16.19.0 running - so I don't think it was that

Thanks! I am embarrassingly still stuck on a simple part: my variables are not coming via msg.payload; retrieving everything from context. So would would this look like if I simply have them defined inside function: let a = acc, let b = 5, let c = true ?

Rather than rewriting the code to allow for data not in msg.payload, rewrite payload to include the data. Could be in a seperate function or at the top of the JiggeryPokery one.

const a = "abc"
const b = 6
const c = global.get("valueOfC") 
msg.payload = {"a": a, "b": b, "c": c}