Wednesday, June 6, 2018

Working with messages

If you want to develop with Node-RED the very first thing you should understand is how to work with messages.  I consider the chapter "Working with Messages" from the Node-RED guide as the most important one for the beginner.

Almost always our flows will modify the msg object that is passed along the nodes. Node-RED has the powerful change node that help us to fit the msg to our needs. However , it is likely that a more complex msg object will require using a function node for that purpose. I show below a use case posted in the Node-RED forum.

Simply stated, convert below object:

[{"Name":"name1","Number":1},{"Name":"name2","Number":2},{"Name":"name3","Number":3}]

to the new object:

[{"name1":1},{"name2":2},{"name3":3}]

First thing to do is to understand the nature of the objects. Note that our initial object is an array of objects. Each element of the array is an object by itself.


{"Name":"name1","Number":1}

{"Name":"name2","Number":2}

{"Name":"name3","Number":3}

Each of these objects have the keys: Name and Number. The desired output is also an array of objects. Each object of the new array will be built using the values from the initial object. The goal is to build a new object using only the property values, therefore discarding the keys Name and Number.

There are many possible ways to write a code tho accomplish such transformation. As you can imagine they will require some iteration (looping).

I describe here four different solutions. All of them using this basic flow: inject node + function node + debug node






First solution: Using the map method


The map method will create a new array by taking each and every element of the original array and processing them with a function code.


This is the JavaScript code inside the function node:

let arr= msg.payload.map(elem => ({[ elem.Name ] :elem.Number}));
msg.payload = arr;
return msg;

The map method will visit each element of the array and will return a modified object, so in the first iteration:

elem = {"Name":"name1","Number":1}

generates:  {"name1":1}

Note that the function in the map method (by the way an arrow function) returns an object in the literal notation {"name1":1} thanks to the use of the square brackets surrounding [elem.Name]. Whenever there is a pair of square brackets like this the variable inside brackets is computed. In this example it simply returns the value of the variable elem.Name which happens to be "name1"

You can learn more on computed properties by clicking in this link.

This is how the debug node displays the result f the flow:



and here is the flow in case you want to import and test:

[{"id":"76c98300.8f224c","type":"debug","z":"d61a402.f83d6c","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":496,"y":191.0000057220459,"wires":[]},{"id":"af7dbb81.8640a8","type":"function","z":"d61a402.f83d6c","name":"map method","func":"let arr= msg.payload.map(elem => ({[elem.Name] :elem.Number}));\nmsg.payload = arr;\nreturn msg;\n","outputs":1,"noerr":0,"x":334.9999828338623,"y":190,"wires":[["76c98300.8f224c"]]},{"id":"c50940a3.ee025","type":"inject","z":"d61a402.f83d6c","name":"","topic":"","payload":"[{\"Name\":\"name1\",\"Number\":1},{\"Name\":\"name2\",\"Number\":2},{\"Name\":\"name3\",\"Number\":3}]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":162.9999828338623,"y":190.00000190734863,"wires":[["af7dbb81.8640a8"]]}]



Second solution: using the for of loop

The for loop is my preferred method to iterate arrays.

This would be the code inside the function node:

let arr = [];
for (let elem of msg.payload) {
arr.push({[ elem.Name ] :elem.Number});
}
msg.payload = arr;
return msg;

The for of loop visits each element of the array, modifies it and push the newly created object to the temporary array named arr. Later this temporary array will be copied to msg.payload.


The exported flow:

[{"id":"e60806b0.8821c8","type":"debug","z":"d61a402.f83d6c","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":540.0000076293945,"y":147,"wires":[]},{"id":"fb75ae0.2e3655","type":"function","z":"d61a402.f83d6c","name":"For of loop","func":"let arr = [];\n\nfor (let elem of msg.payload) {\n    arr.push({[elem.Name] :elem.Number});\n}\n\nmsg.payload = arr;\n\nreturn msg;","outputs":1,"noerr":0,"x":395,"y":146.99999809265137,"wires":[["e60806b0.8821c8"]]},{"id":"18252e58.62eae2","type":"inject","z":"d61a402.f83d6c","name":"","topic":"","payload":"[{\"Name\":\"name1\",\"Number\":1},{\"Name\":\"name2\",\"Number\":2},{\"Name\":\"name3\",\"Number\":3}]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":223,"y":147,"wires":[["fb75ae0.2e3655"]]}]



Third solution: for loop + object destructuring

Same as the previous one with a small change.

let arr = [];
for (let { Name, Number } of msg.payload) {
arr.push({[ Name ] : Number});
}
msg.payload = arr;
return msg;


Here we use object destructuring inside the for of loop to load the variables Name and Number. Those variables will be used right after to create the modified object. We use also computed properties to generate the new objects (square brackets  in the literal object).


Here the Node-RED flow:

[{"id":"199fa305.6b864d","type":"debug","z":"d61a402.f83d6c","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":679,"y":146,"wires":[]},{"id":"4c5fb024.2cac","type":"function","z":"d61a402.f83d6c","name":"Destructuring inside the For of loop ","func":"let arr = [];\n\nfor (let {Name, Number} of msg.payload) {\n    arr.push({[Name] : Number});\n}\n\nmsg.payload = arr;\n\nreturn msg;","outputs":1,"noerr":0,"x":448.99999046325684,"y":146.00000190734863,"wires":[["199fa305.6b864d"]]},{"id":"773b7c94.e78794","type":"inject","z":"d61a402.f83d6c","name":"","topic":"","payload":"[{\"Name\":\"name1\",\"Number\":1},{\"Name\":\"name2\",\"Number\":2},{\"Name\":\"name3\",\"Number\":3}]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":217.9999828338623,"y":147.9999942779541,"wires":[["4c5fb024.2cac"]]}]



Fourth solution: forEach method


let arr= [];
msg.payload.forEach(elem => arr.push({[ elem.Name ] :elem.Number}));
msg.payload = arr;
return msg;

The idea is the same as the previous solutions. The loop will visit each element of the original array and will have it modified by the callback function. The callback function just push the newly created object to the temporary array.


Node-RED flow:

[{"id":"29f9e29f.974c3e","type":"debug","z":"d61a402.f83d6c","name":"Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":555,"y":198,"wires":[]},{"id":"ac334e7d.3faab","type":"function","z":"d61a402.f83d6c","name":"forEach method","func":"let arr= [];\n\nmsg.payload.forEach(elem => arr.push({[elem.Name] :elem.Number}));\n\nmsg.payload = arr;\n\nreturn msg;","outputs":1,"noerr":0,"x":403.9999828338623,"y":196.9999942779541,"wires":[["29f9e29f.974c3e"]]},{"id":"3878f08.c4dc91","type":"inject","z":"d61a402.f83d6c","name":"","topic":"","payload":"[{\"Name\":\"name1\",\"Number\":1},{\"Name\":\"name2\",\"Number\":2},{\"Name\":\"name3\",\"Number\":3}]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":221.9999828338623,"y":196.99999618530273,"wires":[["ac334e7d.3faab"]]}]