Jsonata globalContext() inconsistent handling of string vs variable

When accessing a map object via jsonata globalContext, it does not work when the get method uses a variable - but using a string works.
v 3.0.2


[{"id":"8885e1d87c774eb3","type":"change","z":"f1e84ae7.4dcf98","name":"get with variable","rules":[{"t":"set","p":"payload","pt":"msg","to":"$globalContext(\"testmap\").get(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":877,"y":1033,"wires":[["e28f0de5eb5a8d25"]]},{"id":"f07e5a42e3af87ee","type":"inject","z":"f1e84ae7.4dcf98","name":"variable","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MEQ0838051","payloadType":"str","x":615,"y":1082,"wires":[["8885e1d87c774eb3"]]},{"id":"e28f0de5eb5a8d25","type":"debug","z":"f1e84ae7.4dcf98","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1138,"y":1012,"wires":[]},{"id":"1e367b4a4fcb63fb","type":"inject","z":"f1e84ae7.4dcf98","name":"config","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"2","topic":"","payload":"[{\"hm_addr\":\"MEQ0838051\",\"system\":\"hm\",\"type\":\"lock\"},{\"hm_addr\":\"MEQ0092869\",\"system\":\"hm\",\"type\":\"motion\"}]","payloadType":"json","x":615,"y":993.0000610351562,"wires":[["9ba31ab579766bd2"]]},{"id":"9ba31ab579766bd2","type":"function","z":"f1e84ae7.4dcf98","name":"load map","func":"var m = new Map()\n\nmsg.payload.forEach((item) => {\n    m.set(item.hm_addr,item)\n});\n\nglobal.set(\"testmap\",m);\nreturn null\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":854.9993896484375,"y":964.982421875,"wires":[[]]},{"id":"5c49ee580fc41d67","type":"change","z":"f1e84ae7.4dcf98","name":"get with string","rules":[{"t":"set","p":"payload","pt":"msg","to":"$globalContext(\"testmap\").get(\"MEQ0838051\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":895,"y":1086,"wires":[["e28f0de5eb5a8d25"]]},{"id":"c9776cce0d6852d7","type":"inject","z":"f1e84ae7.4dcf98","name":"string","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"MEQ0838051","payloadType":"str","x":633,"y":1135,"wires":[["5c49ee580fc41d67"]]}]

Clue might lie in the output of this function node?

const x = global.get("testmap");
node.warn(x instanceof Map)
node.warn(x instanceof Object)

const y = new Map()
node.warn(y instanceof Map)
node.warn(y instanceof Object)

Where you will find that it outputs FALSE, FALSE, TRUE, TRUE. Which is rather unexpected.

I think this is because the node get/set functions don't necessarily do what people expect. In that they may serialise/unserialise the data though even I'll admit that I was surprised that the got map was not an instance of Object. I've not yet worked out what it IS an instance of.

OK, no, I'm wrong. That isn't the problem.

The problem is the opposite I believe. I've checked with both memory and file global variables and both do in fact return Map's.

Which means that it is JSONata that can't cope with a Map most likely. Maps are not standard JavaScript objects, they are, as you probably know, a special type of object and so some things we are used to doing with objects do not work.

For example node.warn(Object.getPrototypeOf( new Map() )) will fail with TypeError: encodeObject Error: [Method Map.prototype.entries called on incompatible receiver #<Map>] Value: Object [Map]. But it works fine with a standard object.

And Object.prototype.toString.call(z) will return [object Map] for a Map but [object Object] for a standard object.

Maps are weird. For example, you can do this with a Map: map1.set({'a':1}, 97) that is perfectly valid! If you do console.log([...map1.keys()]), you get something like this: Array ["a", "b", "c", Object { a: 1 }]. Very weird.

I still don't know why this returns FALSE though, it should return TRUE so something odd is happening.

const x = global.get("testmap");
node.warn(x instanceof Map)

And it is still true that you cannot directly serialise a Map with JSON.stringify. It will not work. So if you are using something like the the REDIS store for variables, I think global.set and get will fail.

1 Like

When using the msg variable the map is not in base context so can not see the payload.
So JSONata is looking in correct place.

This is an example why i always use $$ when referencing the msg context.

1 Like

Thank you all for the quick investigation and help!

Ah! I perhaps overlooked something in my response :grin:

I'm glad it works. However I foresee maps creating more problems in the future for Node-RED. I've already fallen foul of them in my own code. Before now, we've mostly assumed we can serialise most things without too many issues and that is less true now.

Wondering if the map() is even needed here, would not a simple object do, the use $lookup(object, payload)

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