Should I use an empty property key? - javascript

I've tested this only in Firefox, but apparently you can use an empty string as a key to a property in an object. For example, see the first property here:
var countsByStatus = {
"": 23, //unknown status
"started": 45,
"draft": 3,
"accepted": 23,
"hold": 2345,
"fixed": 2,
"published": 345
}
In skimming through the EcmaScript specs, it appears that (at least in 5), property keys are defined as strings, and strings as 0 or more characters. This implies that an empty string is a valid property name according to the specs.
Anyway, I'm tempted to use this in a section of code where I'm calculating summaries of some counts by the status of a data item (similar to what I've shown above). There are some items which might not have a status, and I need a placeholder for those. Since statuses are user-definable, I don't want to risk using a dummy word that might conflict.
It seems so simple and elegant, in looking at the data I can easily tell what the blank string would mean. It also makes the code a little bit more efficient, since the empty string would be the exact value of the status in the items without a status.
But at the same time, my instincts are telling me that something is wrong with it. I mean, apart from the chance that some browser might not support this, I feel like I've encountered a bug in JavaScript that will be fixed some day. But, at the same time, that's the same feeling I once had about a lot of other JavaScript features that I now use every day (such as the time I discovered that && and || returns the value of one of the operands, not just true or false).

An object's key must be a string, and the empty string ('') is a string. There is no cross browser issue that I've ever come across with empty strings, although there have been very few occasions where I thought it was acceptable to use an empty string as a key name.
I would discourage the general usage of '' as a key, but for a simple lookup, it'll work just fine, and sounds reasonable. It's a good place to add a comment noting the exceptional circumstance.
Additionally, during lookup you may have issues with values that are cast to a string:
o = {...} //some object
foo = 'bar';
//some examples
o[foo] //will return o['bar']
o[null] //will return o['null']
o[undefined] //will return o['undefined']
If you'd like to have null and undefined use the '' key, you may need to use a fallback:
key = key || '';
If you might have non-string values passed in, it's important to cast too:
key = key || '';
key = '' + key;
note that a value of 0 will turn into '', whereas a value of '0' will stay '0'.
In most cases, I find I'm picking a pre-defined value out of a hashtable object. To check that the value exists on the object there are a number of options:
//will be falsey if the value is falsey
if (o[key]) {...}
//will return true for properties on the object as well as in the prototype hierarchy
if (key in o) {...}
//returns true only for properties on the object instance
if (o.hasOwnProperty(key)) {...}

Technically, there is nothing wrong and you can savely use it on any js engine (that I'm aware of). Since ECMAscripts spec says any object key is a string, it of course can also be an empty string.
The only caveat is, that you'll never be able to access that property with the dot notation
countsByStatus.;
will lead to a syntax error of course, so it always needs to be
countsByStatus[''];
That much about the technical part. If we talk about the convinient part, I'd vote for a very clear no, never use it.
It'll lead to confusion and as we all know, confusion is the enemy.

The problem is that since the statuses are user-defineable there is nothing stoping the user from also using the empty string as a status, thus ruining your logic. From this point of view what you are doing is no different then just using an ugly custom name like __$$unknown_status. (Well, I'd say the ugly custom name is more descriptive but to each its own...)
If you want to be really sure the "unknown" property does not collide you need to keep it separate:
var counts = {
unknownStatus: 23,
byStatus: {
"": 17, //actual status with no name, (if this makes sense)
"started": 45,
"draft": 3,
"accepted": 23,
"hold": 2345,
"fixed": 2,
"published": 345
}
};

I think it's ok. "" has semantics in your application, and its valid javascript. So have at it.
Note that
x."" = 2;
will error out, so you need to use syntax like
x[""] = 2;

Is "unknown status" a null value or is your status field "not null"?
In the first case I'd say you will have to use a separate counter, in the second I'd say that "empty" is a perfectly valid status - just use the word "unknown" for output instead of "". This might only lead to confusion when your user uses the same word as a status type, but to prevent that you only can use a different visual style for "unknown status" output text.

Related

create dynamic ifs from a "query" string

i will try to explain this my best.
I have a data array from many many registers to work on
I have a configuration object, that object have this structure
{ "source": "original",
"hof": "${0} && ${1} && ${2} && (${3} || (${4} && ${5} && ${6} && ${7} && ${8}))",
"filters": {
"0": {
"path": "lead.consent",
"fn": "exists",
"toCompare": true
},
"1": {
"path": "metadata.employee",
"fn": "exists",
"toCompare": true
},
"2": {
"path": "metadata.employee.eligibilityStatus",
"fn": "eq",
"toCompare": "ELIGIBLE"
}
}
}
Inside filters object every "fn" key is a function to apply, every call of those functions will return a boolean value.
So. You can think at HOF like a query to apply to filter the data
The question
Is there any way to do like
data.filter(d=>{
//HERE transform the hof into if statements
})
Example
HOF-> "${0} && ${2}"
data.filter(d => {
//0 is in the objectFilters[0].fn -> nameOfTheFunction -> exist
//2 is in the objectFilters[2].fn-> nameOfTheFunction -> eq
return (exist(d) && eq(d))
})
Thank you VERY much!
A sketch
I said in my comment that you are asking us to do a huge amount of work for you, and that's true. But let's explore what a solution might look like.
First, it's clear that the "hof" key is the top-level "recipe" for the condition. Here's your sample:
"${0} && ${1} && ${2} && (${3} || (${4} && ${5} && ${6} && ${7} && ${8}))"
Each of those ${N} sequences is clearly a placeholder that refers to the "filter" key in the same structure. And those placeholders are embedded in a string that includes boolean operators and grouping, such as && and ( ).
Algorithm
Parse expression to AST
You'll need to parse the "hof" string into an abstract syntax tree (or "AST"). The goal here is to break the overall expression into sub-terms whose truth or falsity can be evaluated independently. Check out boolean-parser-js for a possible solution to that, or at least prior art you can study.
Cash-out placeholders
Next, you'll need to visit each of the terms in the AST to handle its placeholder. (I think a proper AST will have only one placeholder per term.) Your sample data suggests that each placeholder references a predicate descriptor like this:
{
"path": "lead.consent",
"fn": "exists",
"toCompare": true
}
You'll have to create a whole family of small functions designed to handle the many semantically unique variations of these descriptors. One piece of good news is that the "path" key might already be solved for you by lodash's get, which extracts data from a nested object according to a dotted "path" as a string.
You'll probably have a switch(descriptor.fn) that selects the correct comparison implementation. I bet you probably will use Function.prototype.apply to put these three ingredients together to yield a boolean. That boolean then replaces the original string in the source node of the AST.
Condense the AST into a single boolean
Once every node of the AST has been converted into a boolean by evaluating its descriptor in the context of some source object and other data (which I guess is a hash describing a job posting), you'll need to determine the truthiness of the whole expression. I suspect that will be most easily done by a depth-first traversal of the AST, which will collapse the tree until it's a single node that is true or false.
This will be a lot of hard work, but I do believe these are all well-studied problems, and they can all be solved in Javascript (and without the dangerous eval).
I strongly recommend that you write unit tests as you go, because this query engine will be large and complex, and fixing the inevitable bugs will likely break other things because the engine must support so many scenarios. Tests will help you lock down the parts of the engine that work, so you can be certain your "fix" doesn't create a second bug while eliminating the first.

testing whether property exists on an object and is equal to a certain value

let's say that we have an javascript object like below:
let obj = {name: 'firstName'};
which is a better way to test whether a property exists on an object and is equal to something:
1)
if (obj.address === 'someAddress')
or
2)
if (!!obj.address && obj.address === 'someAddress')
Can someone explain which is better/safer and why?
You asked "safer" and "better". "Better" is subjective as long as you don't define what qualities you're looking for, so I'll ignore that part.
Accessing a property that doesn't exist is valid in JavaScript, and simply returns undefined. So the second way is equivalent to:
const address = obj.address
if (!!address && address === 'someAddress') {
...
}
Now you can see that that's plain silly, because the second condition implies the first. In other words, there is no way that address === 'someAddress' can be true and !!address can be false, so there is no need to do the first check at all.
So the second approach is not safer than the first. Both have the same observable effect.
Nitpicker's corner: if you were checking for some falsy value like 0 or "" instead of the truthy string 'someAddress', then the second approach would not even work, because both conditions can never be true at the same time.
Also, if address is a property with an evil getter that may return a different value each time it's called, all bets are off. The first version could actually be safer because it only gets the value once, but presumably the value would be used inside the if block so the code is still broken.
1 is shorter :D and it works :D
Better is:
if (obj?.address === 'someAddress')
it checks both conditions

Javascript arrays and Meteor session

I have made an interesting observation. When trying to update an array that is stored in the Meteor session storage, the following code will not propagate the changes:
var tags = Session.get("Tags");
tags.push("a");
Session.set("Tags", tags);
But if I change the first line to use Session.get("Tags").slice(), everything depending on the session will update accordingly. I guess this is due to the fact that Meteor tests some references for equality and therefore does not update anything.
Is there a better way to manage lists stored in the meteor session store?
If I now try to remove an element from the collection (using array.remove() from here), the behavior turns out to be a bit ... of ... I am doing this inside a Meteor template event, the code looks like this:
"click .taglist li" : function(e) {
var tags = Session.get("Tags").slice();
var index = cardTags.indexOf(this);
Meteor._debug(Session.get("Tags").slice().indexOf("a"));
Meteor._debug("Removing tag \"" + this + "\", index: " + index, ", typeof(this) = " + typeof(this).toString());
tags.remove(index);
Session.set("Tags", tags);
}
This outputs:
1
Removing tag "a", index: -1, typeof(this) = string
So somehow, the cardTags.indexOf(this); statement seems to return -1 for almost any case. I guess I am doing something fundamentally wrong, as I am quite now to javascript, but somehow I can not figure out whats going on here.
Why will those two calls to indexOf() behave different?
I believe this is the same as this situation in Backbone.js. In order for the change event to be triggered, Meteor needs to have a new reference for the array, not just an updated copy of the old one.
In brief, in order to have the 'correct' behaviour, you'll need to clone the array, make the changes you want, and then do Session.set('foo', myCopiedArray).
In short: Use var index = cardTags.indexOf(this.toString()); instead.
Long version:
When using strings in JavaScript, those are strings, whereas typeof 'test' returns string.
Let's take a look at the following code in order to get find out another way to represent strings in JavaScript:
var func = function () {
return this;
};
console.log(func.call('test'));
The console (at least FireBug) won't show us "test", but instead it shows String {0="t", 1="e", 2="s", 3="t" }. typeof would return "object".
The content of the this statement seems to need to be an object. In order to convert a string into a "String" object we can do console.log(new String('test'));, which is the same as the previously logged value.
To convert a string object into a string (data type), just use its prototype toString.

IE8 Javascript: '2' is null or not an object

I'm getting a nonsensical error message in IE8, tell me that the constant '2' is null or not an object. The line of code is:
if (! localtree[idx][2]) {
I also tried coding it like this:
if (localtree[idx][2] == 0) {
The value in the array at that location is always zero (for now).
How can IE8 think that the number 2 is null? I'm mystified!
The exact error is:
Message: '2' is null or not an object
Has anyone seen this?
EDIT : This is a very misleading error message. See my answer below for what actually went wrong.
This is a very confusing error message. It turned out that I was stepping one element beyond the end of the array. 'idx' was referencing a non-existent value that I was attempting to treat as an array reference (with the [2]).
Rather than telling me that '2' was null, it should have said that 'localtree[idx]' was null.
The root cause of this was that I had a trailing comma where I defined the array, leading to an extra, null value in the array. In firefox, trailing commas are ignored (like in perl), but in IE, they are significant.
the constant '2' is null or not an object
if (! localtree[idx][2]) {
JavaScript doesn't have constants, at least not yet. And you aren't checking a number, but a member of an array, i.e.: the variable with index number 2 of object localtree[idx] (where idx must contain a string to refer to an object property or an index number to refer to an array).

Javascript function programming — receiving elaborate parameters

I'm writing a Javascript function that would manipulate an array written on-the-fly and sent as a parameter.
The function is written as follows:
function returnJourney(animation,clean){
var properties = {};
// loads of other inane stuff
for(i in animation[0]) properties[animation[0][i]] = animation[0].i;
// heaps more inane stuff
}
The animation in question is a set of parameters for a jQuery animation. Typically it takes the format of ({key:value,key:value},speedAsInteger,modifierAsString).
So to kick off initial debugging I call it with:
returnJouney(({'foo':'bar'},3000),1);
And straight off the bat things are way off. As far as I see it this would have returnJourney acknowledge clean === 1, and animation being an array with an object as its first child and the number 3000 as its second.
Firebug tells me animation evaluates as the number 3000. What am I doing wrong?
properties[animation[0][i]] = animation[0].i;
should be
properties[animation[0][i]] = animation[0][i];
.i is the property literally called 'i'. As that (probably) doesn't exist, you'll be assigning undefined to each property.
returnJouney(({'foo':'bar'},3000),1);
also makes little sense — do you mean an array?:
returnJourney([{'foo':'bar'},3000],1);
(there is no ‘tuple’ type in JavaScript.)
Also, use var i in rather than the (typo) in in. Forgetting var gives you an accidental global, with potentially annoying-to-debug side-effects.
There's no tuple type in JavaScript. All you have is either object {} or array []. Both of them can understand any mixture of types. So you can either pass your animation parameter as array ([{'foo':'bar'},3000]), which looks like exactly what you wanted.
Or, as it usually done in JavaScript, use object instead:
returnJourney({props: {foo: "bar"}, speed: 3000}, 1);
function returnJourney(animation, clean) {
var props = animation.props;
var speed = animation.speed;
}
Note that object notation let you ignore things you don't want to pass and makes it very clear what value means what.
As for why your animation resolves as 3000, it is really simple, this is how , operator works. It returns the last thing in braces. So (10, 20, 30) would evaluate to 30, so will (f(1000), "hello", 30). Only last value matters, others just ignored (but run anyway, so any side effects will be there).
It's treating ({'foo':'bar'},3000) as an expression using the comma operator, which returns the right operand as its result. Perhaps you meant [{'foo':'bar'},3000].

Categories