Mapping a specific data of an array of properties in JSON

Hi andthanks in advance for your help.

I think I'm crashing of my javascript weaknesses, and I'm always trying to avoid functions.
In this case I am injecting a JSON with with one object, and multiple objects.

For one object I can suimply use the range node, for the array I'm (failing) in using a function.

My questions:

  • how would the correct function be?
  • how could I use the change node instead of a function, using JSONata?

Feel free to point me to any helpful online documentation

[{"id":"aa288e5a.6ead2","type":"inject","z":"63ccda68.bf7a74","name":"insert multiple data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"id\":1,\"first_name\":\"Mariette\",\"last_name\":\"Ulrik\",\"email\":\"mulrik0@gravatar.com\",\"gender\":\"Genderqueer\",\"presence\":75,\"rating\":1,\"experience\":\"Yellow\"},{\"id\":2,\"first_name\":\"Gaspar\",\"last_name\":\"Dunne\",\"email\":\"gdunne1@google.com.au\",\"gender\":\"Agender\",\"presence\":46,\"rating\":3,\"experience\":\"Violet\"},{\"id\":3,\"first_name\":\"Amargo\",\"last_name\":\"Pien\",\"email\":\"apien2@reuters.com\",\"gender\":\"Agender\",\"presence\":95,\"rating\":2,\"experience\":\"Turquoise\"},{\"id\":4,\"first_name\":\"Sylvester\",\"last_name\":\"Dunn\",\"email\":\"sdunn3@abc.net.au\",\"gender\":\"Polygender\",\"presence\":61,\"rating\":1,\"experience\":\"Mauv\"},{\"id\":5,\"first_name\":\"Abbie\",\"last_name\":\"Sharrock\",\"email\":\"asharrock4@reverbnation.com\",\"gender\":\"Genderfluid\",\"presence\":21,\"rating\":1,\"experience\":\"Purple\"},{\"id\":6,\"first_name\":\"Leticia\",\"last_name\":\"Grouvel\",\"email\":\"lgrouvel5@purevolume.com\",\"gender\":\"Female\",\"presence\":38,\"rating\":5,\"experience\":\"Indigo\"},{\"id\":7,\"first_name\":\"Alberta\",\"last_name\":\"Goodfellowe\",\"email\":\"agoodfellowe6@un.org\",\"gender\":\"Female\",\"presence\":78,\"rating\":3,\"experience\":\"Violet\"},{\"id\":8,\"first_name\":\"Alair\",\"last_name\":\"Mindenhall\",\"email\":\"amindenhall7@issuu.com\",\"gender\":\"Non-binary\",\"presence\":30,\"rating\":5,\"experience\":\"Turquoise\"},{\"id\":9,\"first_name\":\"Ulises\",\"last_name\":\"Millan\",\"email\":\"umillan8@symantec.com\",\"gender\":\"Genderfluid\",\"presence\":2,\"rating\":2,\"experience\":\"Purple\"},{\"id\":10,\"first_name\":\"Man\",\"last_name\":\"Brittian\",\"email\":\"mbrittian9@mashable.com\",\"gender\":\"Polygender\",\"presence\":16,\"rating\":2,\"experience\":\"Indigo\"}]","payloadType":"json","x":630,"y":1120,"wires":[["9c83cf10.93e0c"]]},{"id":"e09ad7d8.ebc788","type":"inject","z":"63ccda68.bf7a74","name":"insert one data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"id\":8,\"first_name\":\"Alair\",\"last_name\":\"Mindenhall\",\"email\":\"amindenhall7@issuu.com\",\"gender\":\"Non-binary\",\"presence\":30,\"rating\":5,\"experience\":\"Turquoise\"}","payloadType":"json","x":640,"y":1160,"wires":[["af6577ee.d630b8"]]},{"id":"af6577ee.d630b8","type":"range","z":"63ccda68.bf7a74","minin":"1","maxin":"5","minout":"1","maxout":"1000","action":"scale","round":true,"property":"payload.rating","name":"Map one \"rating\"","x":840,"y":1160,"wires":[["5c57d753.c27ed8"]]},{"id":"5c57d753.c27ed8","type":"debug","z":"63ccda68.bf7a74","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1050,"y":1140,"wires":[]},{"id":"9c83cf10.93e0c","type":"function","z":"63ccda68.bf7a74","name":"Map all \"ratings\"","func":"// Javascript doesn't have built-in support for ranges\n// Insted we use arrays of two elements to represent ranges\n\n// Javascript doesn't have built-in support for ranges\n// Insted we use arrays of two elements to represent ranges\nvar mapRange = function(from, to, s) {\n  return to[0] + (s - from[0]) * (to[1] - to[0]) / (from[1] - from[0]);\n};\n \noutput = {};\n\nfor (var i of msg.payload) {\n    output[i].rating = mapRange([0, 10], [-1, 0], i.rating);\n}\nmsg.payload = output;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":840,"y":1120,"wires":[["5c57d753.c27ed8"]]}]

Something like this is the corrected version of your function

// Javascript doesn't have built-in support for ranges
// Insted we use arrays of two elements to represent ranges

// Javascript doesn't have built-in support for ranges
// Insted we use arrays of two elements to represent ranges
var mapRange = function(from, to, s) {
  return to[0] + (s - from[0]) * (to[1] - to[0]) / (from[1] - from[0]);
};
 
output = []

for (let i=0; i<msg.payload.length; i++) {
    output[i] = {rating: mapRange([0, 10], [-1, 0], msg.payload[i].rating)};
}
msg.payload = output;

return msg;

However, that returns only the scaled rating and loses the rest of the data. Is that what you want to do or do you want to keep the rest of the data intact?

If you still want to avoid using Javascript function nodes, here is a more "node-red" way of scaling multiple objects:

Whether you have 1 or an array of many objects, you can split the incoming array into one object per msg -> use range to scale the value just like you did with one object -> then join the stream back into an array of scaled objects.

1 Like

@shrickus thanks for pointing it out: yes, I mainly want to avoind use functions because I'm feeling quite weak in jasascript (but obviously I will have to face this at some point).
I solved the non coding solution with the profitable combination of split and join, with a range in between.

Still, the question raised by @colin remains: I wanted to change that specific paramenter.
Oviously your function overrides all content of the object.

Why this code is not preserving it (is not touching that parameter)?
How do I achieve the split / join solution?

// Javascript doesn't have built-in support for ranges
// Insted we use arrays of two elements to represent ranges

// Javascript doesn't have built-in support for ranges
// Insted we use arrays of two elements to represent ranges
var mapRange = function(from, to, s) {
  return to[0] + (s - from[0]) * (to[1] - to[0]) / (from[1] - from[0]);
};
 
output = []

for (let i=0; i<msg.payload.length; i++) {
    output[i] = {rating: mapRange([0, 5], [0, 1000], msg.payload[i].rating)};
}
msg.payload.rating = output;

return msg;

My flow:

[{"id":"aa288e5a.6ead2","type":"inject","z":"63ccda68.bf7a74","name":"insert multiple data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"id\":1,\"first_name\":\"Mariette\",\"last_name\":\"Ulrik\",\"email\":\"mulrik0@gravatar.com\",\"gender\":\"Genderqueer\",\"presence\":75,\"rating\":1,\"experience\":\"Yellow\"},{\"id\":2,\"first_name\":\"Gaspar\",\"last_name\":\"Dunne\",\"email\":\"gdunne1@google.com.au\",\"gender\":\"Agender\",\"presence\":46,\"rating\":3,\"experience\":\"Violet\"},{\"id\":3,\"first_name\":\"Amargo\",\"last_name\":\"Pien\",\"email\":\"apien2@reuters.com\",\"gender\":\"Agender\",\"presence\":95,\"rating\":2,\"experience\":\"Turquoise\"},{\"id\":4,\"first_name\":\"Sylvester\",\"last_name\":\"Dunn\",\"email\":\"sdunn3@abc.net.au\",\"gender\":\"Polygender\",\"presence\":61,\"rating\":1,\"experience\":\"Mauv\"},{\"id\":5,\"first_name\":\"Abbie\",\"last_name\":\"Sharrock\",\"email\":\"asharrock4@reverbnation.com\",\"gender\":\"Genderfluid\",\"presence\":21,\"rating\":1,\"experience\":\"Purple\"},{\"id\":6,\"first_name\":\"Leticia\",\"last_name\":\"Grouvel\",\"email\":\"lgrouvel5@purevolume.com\",\"gender\":\"Female\",\"presence\":38,\"rating\":5,\"experience\":\"Indigo\"},{\"id\":7,\"first_name\":\"Alberta\",\"last_name\":\"Goodfellowe\",\"email\":\"agoodfellowe6@un.org\",\"gender\":\"Female\",\"presence\":78,\"rating\":3,\"experience\":\"Violet\"},{\"id\":8,\"first_name\":\"Alair\",\"last_name\":\"Mindenhall\",\"email\":\"amindenhall7@issuu.com\",\"gender\":\"Non-binary\",\"presence\":30,\"rating\":5,\"experience\":\"Turquoise\"},{\"id\":9,\"first_name\":\"Ulises\",\"last_name\":\"Millan\",\"email\":\"umillan8@symantec.com\",\"gender\":\"Genderfluid\",\"presence\":2,\"rating\":2,\"experience\":\"Purple\"},{\"id\":10,\"first_name\":\"Man\",\"last_name\":\"Brittian\",\"email\":\"mbrittian9@mashable.com\",\"gender\":\"Polygender\",\"presence\":16,\"rating\":2,\"experience\":\"Indigo\"}]","payloadType":"json","x":150,"y":1240,"wires":[["4d455c0f.488494","b1779747.a9c788"]]},{"id":"5c57d753.c27ed8","type":"debug","z":"63ccda68.bf7a74","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":630,"y":1220,"wires":[]},{"id":"a6e810c5.38af1","type":"range","z":"63ccda68.bf7a74","minin":"1","maxin":"5","minout":"1","maxout":"1000","action":"scale","round":true,"property":"payload.rating","name":"Map one \"rating\"","x":440,"y":1320,"wires":[["28148edd.286a92"]]},{"id":"d5a86486.8a8188","type":"debug","z":"63ccda68.bf7a74","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":1260,"wires":[]},{"id":"4d455c0f.488494","type":"split","z":"63ccda68.bf7a74","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":true,"addname":"","x":330,"y":1260,"wires":[["a6e810c5.38af1"]]},{"id":"28148edd.286a92","type":"join","z":"63ccda68.bf7a74","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":510,"y":1260,"wires":[["d5a86486.8a8188"]]},{"id":"b1779747.a9c788","type":"function","z":"63ccda68.bf7a74","name":"Map all \"ratings\"","func":"// Javascript doesn't have built-in support for ranges\n// Insted we use arrays of two elements to represent ranges\n\n// Javascript doesn't have built-in support for ranges\n// Insted we use arrays of two elements to represent ranges\nvar mapRange = function(from, to, s) {\n  return to[0] + (s - from[0]) * (to[1] - to[0]) / (from[1] - from[0]);\n};\n \noutput = []\n\nfor (let i=0; i<msg.payload.length; i++) {\n    output[i] = {rating: mapRange([0, 5], [0, 1000], msg.payload[i].rating)};\n}\nmsg.payload.rating = output;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":400,"y":1220,"wires":[["5c57d753.c27ed8"]]}]

To preserve the object use

function

var mapRange = function(from, to, s) {
  return to[0] + (s - from[0]) * (to[1] - to[0]) / (from[1] - from[0]);
};
for (let i=0; i<msg.payload.length; i++) {
    msg.payload[i].rating = mapRange([0, 5], [0, 1000], msg.payload[i].rating);
}
return msg;

JSONata in change node

($to:=[0,1000];	
$from:=[0,5];	
payload ~> |*|{'rating':  ($to[0] + (rating - $from[0]) * ($to[1] - $to[0]) / ($from[1] - $from[0]))}|	)	

Because it creates a new array output which only contains the rating, and returns that in the payload. To keep the original data try this

var mapRange = function(from, to, s) {
  return to[0] + (s - from[0]) * (to[1] - to[0]) / (from[1] - from[0]);
};

for (let i=0; i<msg.payload.length; i++) {
    msg.payload[i].rating = mapRange([0, 10], [-1, 0], msg.payload[i].rating);
}

return msg;

You could also do it a little more neatly using the Array.map() method but that may not be the best solution in this case as it would create a completely new array which is a copy of the original, with the rating changed. That wouldn't matter if the array is small but if there were thousands or tens of thousands of lines then it might not be a good idea. Similarly the split and re-join method would be perfectly ok for small arrays but not a good idea if it were large. It could also be done in JSONata, but again I think it would make a copy of the array like the map function.

thanks both!
I'm testing and editing this.

Thanks!

Colin, good point on the data copy (potential) issue...

In that case, I think the JSONata Object transformation expression from @E1cid would be the best approach, since it changes the data inline, instead of making a copy. At least that's what I get from reading the docs on this admittedly esoteric function...

My reading is that it makes a copy. It says

where
head evaluates to the object that is to be copied and transformed

Thanks both!