Use part of msg.xxx as name of flow variable to be changed

I know this can be done in a Function node, but again I am seeing if this can be done in a Change node, and if not then it may be a item to consider for later builds.
I would like to change/create a context flow variable (but could also be Contect Global, Node or msg.) that changes depending on a variable in the received message (in this case using msg.topic). In this case the msg.topic will be a element of a Object, or array number of an array, so if we could use mustache syntax {{}} the destination variable name would be

flow.inv.{{msg.topic}}.Time.last

or for an array

flow.inv[ {{msg.topicNumber#}} ].Time.last

In the first case msg.topic is text like 'SBEast' so the targert flow variable flow.Inv.SBEast.Time.last would be created or changed.
In the array case msg.topicNumber is number like 0 so the flow variable flow.Inv[0].Time.lastwould be created or changed.
The text form is preferable as I do not need to manage a map of Name to array indices.

Will complement the existing variable access you get to read a Flow variable in JSONATA with something like

$flowContext( "inv." & msg.topic& ".Time.last", file)

Thanks

I think I am getting what you want to do, but am not sure.

Could you write the code for a function node so I can get a better idea?

You can do it in a change node using jsonata with something like this:
$flowContext("begin."&topic&".end")
Here is a sample flow:

[{"id":"11c0b7ef.c83d98","type":"inject","z":"7266ccc6.53673c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":120,"wires":[["6a38e9.5d453718"]]},{"id":"6a38e9.5d453718","type":"change","z":"7266ccc6.53673c","name":"","rules":[{"t":"set","p":"begin.FOO.end","pt":"flow","to":"bar","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":120,"wires":[["c2a89613.a99018"]]},{"id":"c2a89613.a99018","type":"debug","z":"7266ccc6.53673c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":120,"wires":[]},{"id":"5954a2b8.9a9e6c","type":"inject","z":"7266ccc6.53673c","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":220,"y":240,"wires":[["99dd4443.4daab"]]},{"id":"99dd4443.4daab","type":"change","z":"7266ccc6.53673c","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$flowContext(\"begin.\"&topic&\".end\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":240,"wires":[["9c8a1188.1d871"]]},{"id":"9c8a1188.1d871","type":"debug","z":"7266ccc6.53673c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":240,"wires":[]},{"id":"a4d1e1de.523838","type":"catch","z":"7266ccc6.53673c","name":"","scope":null,"uncaught":false,"x":330,"y":340,"wires":[["aa435c00.7014c8"]]},{"id":"aa435c00.7014c8","type":"debug","z":"7266ccc6.53673c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":560,"y":340,"wires":[]}]

Thanks Paul, but that allows me to read what is in the flow data structure base on msg.topic, but JSONATA only produces a value to be stored, it doesn't actually set the location for data to be stored. That is the top line in each element of a Change node which only allows text, no variables (like the value of {{msg.topic}}). More like this which doesn't work.

[{"id":"9c16ea04.71a4a8","type":"inject","z":"3d4070bc.3b7a6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"FOO","payload":"","payloadType":"date","x":148,"y":597.2000198364258,"wires":[["1a121803.a24ec8"]]},{"id":"1a121803.a24ec8","type":"change","z":"3d4070bc.3b7a6","name":"","rules":[{"t":"set","p":"begin.{{msg.topic}}.end","pt":"flow","to":"bar","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":408,"y":597.2000198364258,"wires":[["179c5f24.1a3fe1"]]},{"id":"179c5f24.1a3fe1","type":"debug","z":"3d4070bc.3b7a6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":638,"y":597.2000198364258,"wires":[]},{"id":"4cafec82.f6aad4","type":"inject","z":"3d4070bc.3b7a6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"FOO","payload":"","payloadType":"date","x":136,"y":652.2000122070312,"wires":[["543b2a1e.bd2d34"]]},{"id":"543b2a1e.bd2d34","type":"change","z":"3d4070bc.3b7a6","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$flowContext(\"begin.\"&topic&\".end\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":356,"y":652.2000122070312,"wires":[["159d061a.fdfcaa"]]},{"id":"159d061a.fdfcaa","type":"debug","z":"3d4070bc.3b7a6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":646,"y":652.2000122070312,"wires":[]},{"id":"731b6066.07be","type":"catch","z":"3d4070bc.3b7a6","name":"","scope":null,"uncaught":false,"x":226,"y":752.2000122070312,"wires":[["d72bd163.83dcb"]]},{"id":"d72bd163.83dcb","type":"debug","z":"3d4070bc.3b7a6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":456,"y":752.2000122070312,"wires":[]}]

Yup, you're right. In the change node, you can have a substitutable name on a 'set' to a context variable but there doesn't seem to be a way do do it when you want to 'get' the data since the name of the contect variable has to be hard coded.

@dceejay any thoughts?

In jsonata you can use backticks in properties names to evaluate them:

Still, this is a strange excercise. Why not use properties to describe the property.

@bakman2 the OP wants to dynamically specify the context name of the flow variable dynamically. i.e.
flow.begin.????.end where ???? could be 'kitchen' or' den' or what ever.

This can be done when you get a context variable because you can use jsonata
Screen Shot 2020-08-19 at 9.16.23 AM
but you can not set the value using a dynamically created name since there is no jsonata option for that

Screen Shot 2020-08-19 at 9.13.09 AM

Try the expression from my response, it works like OP requested.

I think the issue is that while what you wrote might work, it would also delete any existing objects in flow.inv.

I have several situations where I want to store last MQTT value against its topic and do something like this in a function node...

var store = flow.get("store") || {}; //get the store object
store[msg.topic] = msg.payload; //store the payload against its topic (like a lookup)
flow.set("store",msg.payload); //save the store

I believe what the OP is asking for is a way to do this in a change node without destruction or re-creating the store object in context - something like this...

SET   flow.store[{{msg.topic}}]
TO    msg.payload

Disclaimer, I did not try your JSONata expr - forgive me if it does exactly this already.

@bakman2 I didn't know you could do that with back-tic's (learn something new every day - thanks :+1:) One change in your example - since it seems the OP wants multiple levels, I beleive it should be {topic:{"Time":{"last":last}}}

@Steve-Mcl rereading the original post, I think you are right that he just wants to update the one part of the context variable so to do it in a change node you would first have to get it, then change it then put it and it would be messier than a simple funcion loke your example.

@IanH could you clarify if you want the change node to only insert/change a portion of the contect variable or replace the whole thing with what you provide?

1 Like

@zenofmud , thanks for your help, and getting the right techno speak. Yes, I am trying to change the Context stored object, whether it is replacing a element in the object or adding new sub objects to the Parent object in Context.

@bakman2, thanks for the relevation of using the backtic in JSONata, a behaviour I had not found yet. But again it is part of JSONata, so can be used to read a variable Context variable but not set it. But it can still help, but will require three operations in a change node rather than one (ie, SET the context variable to a temporary variable, change the temporary variable by adding, changing the temporary Variable using backtick, then replace the Context Variable with the updated temporary variable. That is progress. I have managed to add a new but of structure (as in example, I added Study using $merge) but not yet managed to change an existing Value (I tried to change Kitchen.Temp but just ended up loosing data like Start and Stop!)

[{"id":"a2127890.70a508","type":"inject","z":"d05da15.a8b596","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Study","payload":"15.5","payloadType":"num","x":195,"y":155,"wires":[["64754b1c.bb2784"]]},{"id":"64754b1c.bb2784","type":"change","z":"d05da15.a8b596","name":"","rules":[{"t":"set","p":"House","pt":"msg","to":"House","tot":"flow"},{"t":"set","p":"House","pt":"msg","to":"$merge([House, {`topic`:{\"Temp\":`payload`}}])\t","tot":"jsonata"},{"t":"set","p":"House","pt":"flow","to":"House","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":425,"y":155,"wires":[["257945c7.16b17a"]]},{"id":"257945c7.16b17a","type":"debug","z":"d05da15.a8b596","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":685,"y":155,"wires":[]},{"id":"1e4648bf.768787","type":"inject","z":"d05da15.a8b596","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Study","payload":"","payloadType":"date","x":203,"y":209.99999237060547,"wires":[["13135251.45720e"]]},{"id":"13135251.45720e","type":"change","z":"d05da15.a8b596","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$flowContext(\"House.\"&topic&\".Temp\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":423,"y":209.99999237060547,"wires":[["354f8270.ffecde"]]},{"id":"354f8270.ffecde","type":"debug","z":"d05da15.a8b596","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":713,"y":209.99999237060547,"wires":[]},{"id":"ab266f1b.4a31c","type":"catch","z":"d05da15.a8b596","name":"","scope":null,"uncaught":false,"x":293,"y":309.99999237060547,"wires":[["da3e8088.d4077"]]},{"id":"da3e8088.d4077","type":"debug","z":"d05da15.a8b596","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":523,"y":309.99999237060547,"wires":[]},{"id":"3e14581f.2ac1e8","type":"inject","z":"d05da15.a8b596","name":"Initiate Flow Variable","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":220,"y":93,"wires":[["88e7b4d0.31e158"]]},{"id":"88e7b4d0.31e158","type":"change","z":"d05da15.a8b596","name":"","rules":[{"t":"delete","p":"House","pt":"flow"},{"t":"set","p":"House","pt":"flow","to":"{\"Lounge\":{\"Temp\":25,\"Start\":500,\"Stop\":1600},\"Kitchen\":{\"Temp\":25,\"Start\":500,\"Stop\":1600}}","tot":"json"},{"t":"set","p":"payload","pt":"msg","to":"House","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":93,"wires":[["257945c7.16b17a"]]}]

@Steve-Mcl, I am changing either existing elements in the structure of the Context Variable, or adding new sub elements to it. The whole idea of what I propose is that one node can process flows for several entities (ie rooms in a house) without it knowing what are the rooms when programmed, rather than having a node for each room, and needing to add a node when a new room is added to that being managed. The context flow variable would contain information in perpetual storage (like set temperature for room heating, schedule time for on/off, identity (ie MQTT topic) of any actuator to turn heat on/off etc).
I do have this working in a function node, and a bit simpler code than yours above Steve, I find flow.set() on an object will change just that part of the object if it exists, but otherwise will add it.

I am not sure why you want to use an object vs an array of objects. I understand that it might "appear" easier when changing/modifying the object. An object is a container with properties that describe the property, ie; "room": "study".

But for optimization, you can reduce the change node to 1 single rule:

set flow.House:

$merge([$flowContext("House"), {`topic`:{"Temp":payload}}])

flow:

[{"id":"f571388e.fafc08","type":"inject","z":"d4cd4f35.ec0ea","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Study","payload":"15.5","payloadType":"num","x":132,"y":240,"wires":[["ab54f78d.c85108"]]},{"id":"ab54f78d.c85108","type":"change","z":"d4cd4f35.ec0ea","name":"","rules":[{"t":"set","p":"House","pt":"flow","to":"$merge([$flowContext(\"House\"), {`topic`:{\"Temp\":payload}}])\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":368,"y":240,"wires":[["df13357b.6f005"]]},{"id":"df13357b.6f005","type":"debug","z":"d4cd4f35.ec0ea","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":670,"y":240,"wires":[]},{"id":"ee1230df.4a58c","type":"inject","z":"d4cd4f35.ec0ea","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Study","payload":"","payloadType":"date","x":152,"y":288,"wires":[["95f7aa16.bac0b8"]]},{"id":"95f7aa16.bac0b8","type":"change","z":"d4cd4f35.ec0ea","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"$flowContext(\"House.\"&topic&\".Temp\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":368,"y":288,"wires":[["693bd3aa.6e44f4"]]},{"id":"693bd3aa.6e44f4","type":"debug","z":"d4cd4f35.ec0ea","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":550,"y":288,"wires":[]},{"id":"6a6899bd.1f612","type":"inject","z":"d4cd4f35.ec0ea","name":"Initiate Flow Variable","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":162,"y":192,"wires":[["c47862c9.e759c8"]]},{"id":"c47862c9.e759c8","type":"change","z":"d4cd4f35.ec0ea","name":"","rules":[{"t":"delete","p":"House","pt":"flow"},{"t":"set","p":"House","pt":"flow","to":"{\"Lounge\":{\"Temp\":25,\"Start\":500,\"Stop\":1600},\"Kitchen\":{\"Temp\":25,\"Start\":500,\"Stop\":1600}}","tot":"json"},{"t":"set","p":"payload","pt":"msg","to":"House","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":368,"y":192,"wires":[["df13357b.6f005"]]}]

Hey @bakman2,

In this kinda situation (and as in mine too) using an array is not suitable tbh. The dynamic/unknown aspect would be hindered having to search an array each time a value lookup was performed. Accessing values from an object by bracket notation to perform random access lookups is far faster and easier than array searches.

Thanks all. I have cracked it. In a nutshell it required a $map with embedded $merge function in JSONata plus moving the context store to msg and back to context all within one Change Node. Yep a single function node may be simpler.

I have published as a part of a larger ui Flow at https://flows.nodered.org/flow/1bf2ef4514786619508654a1bcd9098e

In a nutshell, starting with a context flow variable called SHW which contains an array of objects with each object containing a key and one or more variables {"key":"aString", "current": 123}. msg.ParamKey is used to select the array object required. The new value for the variable is in msg.payload.

  1. Copy flow.SHW to msg.SHW
  2. Use JSONata to extract SHW[key=$$.ParamKey] to msg.Param
  3. Copy msg.payload to msg.Param.current
  4. Use JSONata to update flow.SHW with
    $map( SHW, function($v, $i){ $v.key!=$$.ParamKey ?$v :$merge([$v, {"current":payload}]) } )

Thanks all for your help, we all still learnt little quirks about Node-Red and JSONata that are not documented or buried in the documentation which is what these forums are about.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.