Current Behavior
The range node coerces its input with Number(value) and only rejects the result if it is NaN (16-range.js):
var n = Number(value);
if (!isNaN(n)) { ...scale and send... }
else { node.log(RED._("range.errors.notnumber")+": "+value); }
Because of JavaScript's coercion rules, several non-numeric inputs pass that check and get scaled as if they were real readings:
input msg.payload |
Number(value) |
range node output (scale 0–100 → 0–1) |
null |
0 |
0 — sent as a valid reading |
true |
1 |
0.01 |
false |
0 |
0 |
"" (empty string) |
0 |
0 |
" " (whitespace) |
0 |
0 |
[] |
0 |
0 |
"abc" |
NaN |
dropped + "notnumber" log (correct) |
undefined / missing |
— |
passed through unchanged |
With action "drop if out of range", a null is not dropped whenever 0 is inside the input range. It comes out as a real 0.
Expected Behavior
Only numbers, and strings that actually contain a number, should be scaled. null, booleans, empty or whitespace-only strings and arrays/objects should get the same handling "abc" already gets: not sent, with the existing "notnumber" log line. Or, if you'd rather keep the node forgiving, the behaviour could at least be documented in the node's help text. At the moment the help only says the node scales numeric values.
A possible check, keeping numeric strings working:
function toNumber(v) {
if (typeof v === "number") return v;
if (typeof v === "string" && v.trim() !== "") return Number(v);
return NaN;
}
var n = toNumber(value);
Why it matters (real-world impact)
Sensors that report a fault as JSON null are common. MQTT → json → change → range (drop, −5…45) → average-over-time → InfluxDB is a typical telemetry pipeline, and in it every null became a 0 reading. Mixed into a 10-minute average, one null among k real readings produced a dip of exactly k/(k+1) of the true value. A window of all nulls produced a flat 0. In our case, several years of a pool-temperature series collected hundreds of these fake dips before the cause was found. The data never looked like an error, only like a slightly cold reading.
Boolean inputs have the same problem in a different way: a status flag wired to a range node by mistake silently turns into 0/1 scaled values instead of being rejected.
Steps To Reproduce
Import this flow, deploy, and click each inject node. Look at the debug sidebar and the Node-RED log.
[
{"id":"inj","type":"inject","name":"inputs","props":[],"repeat":"","once":false,"wires":[["fn"]]},
{"id":"fn","type":"function","name":"null/true/false/\"\"/abc/42","func":"for (const v of [null, true, false, \"\", \" \", [], \"abc\", \"42\", 42]) {\n node.send({ payload: v, input: JSON.stringify(v) });\n}\nreturn null;","outputs":1,"wires":[["rng"]]},
{"id":"rng","type":"range","name":"drop, 0-100 -> 0-1","minin":"0","maxin":"100","minout":"0","maxout":"1","action":"drop","round":false,"property":"payload","wires":[["dbg"]]},
{"id":"dbg","type":"debug","name":"","active":true,"complete":"true","wires":[]}
]
Result: the debug node shows outputs for null (0), true (0.01), false (0), "" (0), " " (0) and [] (0), as well as for the real inputs "42" and 42 (0.42). Only "abc" is dropped.
Example flow
See above.
Environment
- Node-RED version: 5.0.7
- Node.js version: 24.20.0
- npm version: 11.19.1
- Platform/OS: Debian
- Browser: n/a (runtime behaviour)
Checked against the current master source of packages/node_modules/@node-red/nodes/core/function/16-range.js (latest release at time of writing: 5.0.7).
Current Behavior
The range node coerces its input with
Number(value)and only rejects the result if it isNaN(16-range.js):Because of JavaScript's coercion rules, several non-numeric inputs pass that check and get scaled as if they were real readings:
msg.payloadNumber(value)null00— sent as a valid readingtrue10.01false00""(empty string)00" "(whitespace)00[]00"abc"NaNundefined/ missingWith action "drop if out of range", a
nullis not dropped whenever0is inside the input range. It comes out as a real0.Expected Behavior
Only numbers, and strings that actually contain a number, should be scaled.
null, booleans, empty or whitespace-only strings and arrays/objects should get the same handling"abc"already gets: not sent, with the existing "notnumber" log line. Or, if you'd rather keep the node forgiving, the behaviour could at least be documented in the node's help text. At the moment the help only says the node scales numeric values.A possible check, keeping numeric strings working:
Why it matters (real-world impact)
Sensors that report a fault as JSON
nullare common. MQTT →json→ change → range (drop, −5…45) → average-over-time → InfluxDB is a typical telemetry pipeline, and in it everynullbecame a0reading. Mixed into a 10-minute average, onenullamong k real readings produced a dip of exactly k/(k+1) of the true value. A window of all nulls produced a flat 0. In our case, several years of a pool-temperature series collected hundreds of these fake dips before the cause was found. The data never looked like an error, only like a slightly cold reading.Boolean inputs have the same problem in a different way: a status flag wired to a range node by mistake silently turns into 0/1 scaled values instead of being rejected.
Steps To Reproduce
Import this flow, deploy, and click each inject node. Look at the debug sidebar and the Node-RED log.
[ {"id":"inj","type":"inject","name":"inputs","props":[],"repeat":"","once":false,"wires":[["fn"]]}, {"id":"fn","type":"function","name":"null/true/false/\"\"/abc/42","func":"for (const v of [null, true, false, \"\", \" \", [], \"abc\", \"42\", 42]) {\n node.send({ payload: v, input: JSON.stringify(v) });\n}\nreturn null;","outputs":1,"wires":[["rng"]]}, {"id":"rng","type":"range","name":"drop, 0-100 -> 0-1","minin":"0","maxin":"100","minout":"0","maxout":"1","action":"drop","round":false,"property":"payload","wires":[["dbg"]]}, {"id":"dbg","type":"debug","name":"","active":true,"complete":"true","wires":[]} ]Result: the debug node shows outputs for
null(0),true(0.01),false(0),""(0)," "(0) and[](0), as well as for the real inputs"42"and42(0.42). Only"abc"is dropped.Example flow
See above.
Environment
Checked against the current
mastersource ofpackages/node_modules/@node-red/nodes/core/function/16-range.js(latest release at time of writing: 5.0.7).