No periods in context variable names?

Not sure why I have not seen this before, but I was writing a flow that need use of context, so I did the following... Where msg.topic just happened to be FQDN reference, say www.google.com.

var theDevice = flow.get(msg.topic) || {};

theDevice.ping = msg.payload;

if (typeof theDevice.exclude === undefined)
    theDevice.exclude = false;

flow.set(msg.topic, theDevice);

return msg;

When I looked at the context, I found...
image

I don't yet have a lot of experience with JS, but I take it somewhere there is a rule that says periods can't be part of a variable name, because they are parsed as part of the property (tree). But why is context doing this when context requires a string?

For example, you do flow.get('somestring') or flow.set('somestring', theValue). If 'somestring' has a period in it... that should not be an issue. It is a string, not a variable. Or is this a case where I have to do $(msg.topic} to avoid the parsing? That did not work either.

Of course I then tried a literal string flow.set('www.google.com'), and that also was parsed, IMHO that just does not make sense at face value, JS variable rules not withstanding. So is this something unique to JS? Or even NR? What if I really want a flow reference label to have a period in it? Is that not possible, or such?

Of course an object property key can have a period in it, for example "www.google.com: true" can be done. Even { "www.google.com": { "www.microsoft.com": false, "red.hat.com": true} } can be done.

For example...

theDevice['this.is.a.test']='test';
var theTest = theDevice['this is a test'];

The above shows up as 'this.is.a.test': 'test' in context.

Context variable names should be valid JavaScript identifiers.

This is because you can do things like this:

var myObject = { "foo": 1, "bar": 2 };
flow.set("test", myObject);

// Get individual values
var foo = flow.get("test.foo");
var bar = flow.get("test.bar");

// Update just bar:
flow.set("test.bar", 3);

It also works with arrays:

var myArray = [1,2,3,4];
flow.set("test", myArray);

var firstElement = flow.get("test[0]");
var secondElement = flow.get("test[1]");

flow.set("test[2]", 10);
2 Likes

So this was by design, which is cool. I just had not tripped over it as yet. It means my flow either creates some funky dot noted trees... which work but are not needed, or I change the logic to not use FQDN as 'keys' as I thought I would.

If you read up on JavaScript identifiers, you will find that you can use periods. However, it complicates matters. That's because you can no longer refer to your property using dotted naming.

const odd = { "normal": 1, "not.normal": 2 }

console.log( odd.normal ) // This works just fine
console.log( odd.not.normal ) // This does not work
console.log( odd["not.normal"] ) // But this does work
1 Like

I see that is is possible to have embedded dots in property names, but I can't see how to define a variable name with a dot. This does not seem to be valid, according the editor.
var odd.name = 2
However I can't find any reference that says it is not valid in variable names.

vars end up in global in node (window in browser)

so to have a var of "odd.name" you need to use bracket notation.

image

If you really want to get into the fine detail, you have to dig into the ECMAScript spec - https://ecma-international.org/ecma-262/6.0/#sec-object-initializer

And you have to get a bit more precise on terminology.

var myObject =  { myProperty: 123 }

Here, myObject is an identifier and myProperty is a property.

The rules are different for what is a valid identifier and what is a valid property.

Identifier:

In ES2015, identifiers must start with $ , _ , or any symbol with the Unicode derived core property ID_Start .
The rest of the identifier can contain $ , _ , U+200C zero width non-joiner, U+200D zero width joiner, or any symbol with the Unicode derived core property ID_Continue .

Property:

A property name can be either an identifier name (i.e. identifiers + reserved words), a string literal , or a numeric literal .

This means it is perfectly valid to have . in a property name, as long as it is written as a string literal - ie with surrounding quotes of some type.

But you cannot have a . in a top-level identifier.

This post gives a lot more detail (and is where I copied the extracts above from): https://mathiasbynens.be/notes/javascript-identifiers-es6

1 Like

Which takes this full circle... in effect, 'reference' (using the term loosely) names do not follow the same 'rules' as variables. Which is fine, it just is not intuitive when context references could be seen as 'references', but are in effect 'structured' variables. This is reasonable in the fact that referencing context is done by a string literal typically. But as was noted above, that is not the only use case, in that said string is parsed as a variable, i.e. variable with or without property reference(s). Thus 'www.google.com' is not a literal per the context parsing. but a variable with properties, such that on context it is really www->google->com = whatever. Even when I did bracket the string literal in some type of quotes, it was still parsed. I don't recall from memory is this documented as such? But as a new to JS, if not NR, this did trip me up until I realized what the context parsing was doing.

I think that it is documented but yes, it is very confusing even for experienced programmers.

However, the fact that you can use periods and other characters in property names can be really useful and can save a whole bunch of programming - for example, when making use of MQTT topics with "/" as the typical level separator and storing them in Node-RED as properties.

It is also nice that Node-RED's global/flow/context variable getter and setter work with those complex property names. Inevitably however, the written code gets a bit confusing. Rather like when you mix JavaScript code into HTML attributes when using Angular, Vue or REACT and you have to nest quotes.

Oh, I am not suggesting anything or such, the flexibility is good, it is just a surprising result when you have done 100 string literals, that did not happen to have a period in the string, to do such, and see the context variable parsed as noted. My initial reaction was, 'What the hell is this?' I happened to say in English, so my dachshunds pretty much ignored it. But when I later said 'Das ist einfach nicht richtig' the dachshunds looked at me, as if to say 'Was zum Teufel'. Translation [That is just not right] [What the heck (hell, devil)].

And this concludes the German language lesson for today. [OTFL][

1 Like

Never hurts to have a language lesson :slight_smile: Sadly my German goes as far as ordering the beers and a few other basics that are required for skiing in Switzerland.

You should be able to work around the issue by using a container object it the context. Something like:

var devices = flow.get('devices') || {};
var theDevice = devices[msg.topic] || {};
... 
devices[msg.topic] = theDevice;
flow.set('devices', devices);

It's not the same thing as you'll retrieve the reference to the container containing all the devices instead of only one but I doubt it will have (nearly) any performance implications.

Yup - adding a top level object would work - and the flow.set/get statements can be streamlined to:

flow.set(`devices["${msg.topic}"]`) = 'hello there';
2 Likes

Good point. I forgot the context functions handle nested values along the path both ways.

Cool. Really did not intent to spark some much effort on this. I really can live without the periods. :slight_smile: But this just affirms this forum, when it gets a bone, often just never wants to let go!

When I use persistence... I often use a object structure, so I have one identifier, and a data structure behind it. Not sure where I developed such a habit, but it makes things easier, I think. It maybe just because when I need persistence, I happen to have a lot of data to handle, versus a variable here,or there type of thing.

1 Like