Unintentionally changes in a flow variable (without flow.set)

hi,

I am assigning the content of a flow-variable to a new variable.
it looks like, on changing in the new variable, the content of the flow variable is also changed without any flow.set in between.

I think I have no logic error, I do not get where I change the flow variable

attached 4 flows to demonstrate.

the part which will change is the active from true to false:

image

[{"id":"7596cb0ce1eff1ce","type":"function","z":"df7c9eb28d26a1c2","name":"setflowvar","func":"var existingtimeplan = JSON.parse('[{\"Active\":false,\"Power\":0,\"ScheduleType\":\"CHARGE_MAX\",\"TimeTable\":{\"Start\":\"00:00\",\"End\":\"00:01\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}},{\"Active\":false,\"Power\":0,\"ScheduleType\":\"CHARGE_MAX\",\"TimeTable\":{\"Start\":\"00:01\",\"End\":\"00:02\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}},{\"Active\":true,\"Power\":500,\"ScheduleType\":\"CHARGE_MAX\",\"TimeTable\":{\"Start\":\"06:00\",\"End\":\"12:00\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}},{\"Active\":true,\"Power\":2000,\"ScheduleType\":\"CHARGE_MAX\",\"TimeTable\":{\"Start\":\"12:00\",\"End\":\"17:00\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}},{\"Active\":false,\"Power\":10000,\"ScheduleType\":\"CHARGE_MAX\",\"TimeTable\":{\"Start\":\"19:00\",\"End\":\"22:00\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}},{\"Active\":false,\"Power\":9000,\"ScheduleType\":\"DISCHARGE_MIN\",\"TimeTable\":{\"Start\":\"17:00\",\"End\":\"23:00\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}},{\"Active\":false,\"Power\":2000,\"ScheduleType\":\"DISCHARGE_MIN\",\"TimeTable\":{\"Start\":\"19:00\",\"End\":\"23:00\"},\"Weekdays\":{\"Mon\":true,\"Tue\":true,\"Wed\":true,\"Thu\":true,\"Fri\":true,\"Sat\":true,\"Sun\":true}}]')\n\nflow.set('existing', existingtimeplan)\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":540,"wires":[[]]},{"id":"77bcbd1029343d15","type":"inject","z":"df7c9eb28d26a1c2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":520,"y":500,"wires":[["7596cb0ce1eff1ce"]]},{"id":"64d068f710b094de","type":"function","z":"df7c9eb28d26a1c2","name":"useflowvar","func":"var mytimeplanentry = {\n    'Active': true,\n    'Power': 2000,\n    'ScheduleType': \"CHARGE_MAX\",\n    'TimeTable': {\n        'End': \"20:00\",\n        'Start': \"06:00\"\n    },\n    'Weekdays': {\n        'Fri': true,\n        'Mon': true,\n        'Sat': true,\n        'Sun': true,\n        'Thu': true,\n        'Tue': true,\n        'Wed': true\n    }\n};\nvar newtimeplan = []\nnewtimeplan.push(mytimeplanentry) // first add mytimeplanentry\nvar storedtimeplan = flow.get('existing')\nfor (var timeplans in storedtimeplan) {\n    //Deactivate all Plans and put them behind mytimeplanentry\n    storedtimeplan[timeplans]['Active'] = false;\n    newtimeplan.push(storedtimeplan[timeplans])\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":540,"wires":[[]]},{"id":"7629b19851eac8c9","type":"inject","z":"df7c9eb28d26a1c2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":700,"y":500,"wires":[["64d068f710b094de"]]}]

This is a consequence of the how the in-memory context works. The context.get() call returns the object from context - it doesn't clone the value, so it gives you exactly what is held in context. This means modifications you make are made to the object held in context, even without the .set() call.

If you want to avoid this modification, you need to clone the returned object yourself.

To add to that a bit, you can think of the variable containing pointer to object, rather than a copy (clone) of the object. This is the way javascript works with objects, so for example, in this code

let v1 = {x: 3, y: 4}
let v2 = v1

both v1 and v2 point to the same object, so if this is followed by

v1.x = 4

then v2.x would also now be 4. Node red provides a clone method, so you could use
let storedtimeplan = RED.util.cloneMessage(context.get("existing"))
to get a copy of the object in context.

By the way, your code is not importable as the forum has interpreted it as markdown, in order to make code readable and usable it is necessary to surround the code with three backticks (also known as a left quote or backquote ```)

``` 
   code goes here 
```

You can edit and correct your post by clicking the pencil :pencil2: icon.

See this post for more details - How to share code or flow json

Edit: Corrected code above as suggested by @jbudd below

Should that be like this?

let v1 = {x: 3, y: 4}
let v2 = v1

Oops yes, I will correct it. Thanks.

hi,

thank you for your help. :slight_smile:

Have corrected the sample flows to be view- and importable.

but, there is an difference between objects and values anyhow?

[{"id":"3eacbca25217a930","type":"inject","z":"df7c9eb28d26a1c2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":580,"y":600,"wires":[["2b359338c807f133"]]},{"id":"2b359338c807f133","type":"function","z":"df7c9eb28d26a1c2","name":"function 1","func":"flow.set('myvar', 42)\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":580,"y":640,"wires":[[]]},{"id":"802dc6a019b56c27","type":"inject","z":"df7c9eb28d26a1c2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":760,"y":600,"wires":[["7dc579266d5f3948"]]},{"id":"7dc579266d5f3948","type":"function","z":"df7c9eb28d26a1c2","name":"function 2","func":"var value = flow.get('myvar');\nnode.warn(value);\nvalue = 43;\nnode.warn(flow.get('myvar'))\nnode.warn(value);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":640,"wires":[[]]}]

here I get the output as expected.

how can I distinguish between a "copied" and a "linked" object or value?

is e.g. the above flowvar also affected?
image

more, as your samples are also with normal variables (and not only flow variables), I am now a bit concerned of how my tons of functions behave and how I can find and fix such unintentionally links instead of clones (in my head they were until now always clones :scream: )

thanks! :slight_smile:

Yes, simple values, that is Numbers, Booleans, Strings, bigint and symbol are copied, Objects, Arrays, and Buffers are addressed by Reference. They are the types that have this feature. I believe the main reason for doing it is that it is normally ok and vastly improves the efficiency of the code. Occasionally it is a nuisance. This is a feature of javascript, not specifically node-red.

By considering the type.

It is very rarely an issue. The fact that you have only just seen this shows that it is a rare problem. When it is an issue, usually it is obvious that there is something wrong, though it can be tricky to work out what is going on.

thank you all for your help :slight_smile:

1 Like