create dynamic ifs from a "query" string - javascript

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.

Related

How to use enum-like javascript-class that assigns its values without constructor

I have this class, which is supposed to represent an enum:
export default class ChangeStatusEnum {
"added" = "added";
"deleted" = "deleted";
"edited" = "edited";
"unedited" = "unedited";
static constructFromObject(object) {
return object;
}
}
It is generated in the pipeline by openapi-generator1 so changing it is not an option. This is not a question on best practices for defining enums in vanilla-js or typescript, this question is about how to use this class.
I do not understand the syntax of assigning to a string, I do not know where these four strings are accessible.
Here are a few things that I have tried (in a jenkins-test so they can be run easily):
test("access", () => {
console.log(ChangeStatusEnum) // prints [class ChangeStatusEnum]
console.log(JSON.stringify(ChangeStatusEnum)) // prints undefined
console.log(
ChangeStatusEnum.constructFromObject("deleted") === "deleted"
) // prints true
console.log(
ChangeStatusEnum.constructFromObject("nonexisting") === "nonexisting"
) // also prints true, which means this syntax has no value over just using strings instead of enums
console.log(ChangeStatusEnum["added"]) // prints undefined
console.log(ChangeStatusEnum.added) // prints undefined
})
The least I expect from a datastructure that calls itself "enum" is that I can construct and compare values of it without fear of silently constructing non-existing values. Iterating over all values of an enum would also be nice, but is not strictly necessary.
I suppose there is a way to do that with this datastructure that I just do not know of due to lack of knowledge of advanced javascript-constructs.
1 The tool is openapi-generator-cli in version 2.5.1 https://www.npmjs.com/package/#openapitools/openapi-generator-cli with openapi-generator-maven-plugin version 6.0.0 https://mvnrepository.com/artifact/org.openapitools/openapi-generator-maven-plugin
Since this is somewhat mature tooling I expect their enum-solution to be usable, this is why I ask this question as a js-question and not as an openapi-question.
I think the most elegant way to use it is to create an instance of it and use it as a constant. The 'constructFromObject'-function is not needed for this. So just put this below the imports:
const changeStatusEnum = new ChangeStatusEnum();
Afterwards, the members can be accessed using normal dot notation:
changeStatusEnum.added // evaluates to the string "added"

How do I compare code to one another in a console?

I am new to learning code, so I do not know what to do. Through my course online I discovered that you can compare text using ==
So when I put "yes" == "yes" the console will say True
It's a pretty neat feature, but back on topic.
How can I compare code to one another? For example, I want to compare:
"The file located at \"C:\\Desktop\My Documents\Roster\names.txt\" contains the names on the roster."
To another code, but with it telling me if it's similar or not. How do I do it? I read somewhere about using Escaping Strings, but I don't know where to place them.
I appreciate the help,
Zeke
Testing for equality compares data on each side of the operator.
== is the loose equality operator.
=== is the strict equality operator.
They differ in how they respond to arguments.
Example:
The string "9" and the number 9 are evaluated by loose equality operator as true
"9" == 9 // returns true
However, the strict equality operator, as its name might suggest, also compares the type of data (string vs. number)
"9" === 9 // returns false
More examples and cases can be found here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
For comparing the contents of a file, my suggestion in your case would be to parse the file from .json, so that you can treat it as a JavaScript object. Then you can compare the contents with strings of names.

Are the elements of a javascript object initialized in a specific order?

I have a function like that:
parsers[1] = function(buf) {
return {
type: "init",
name: buf.readUTF8String(),
capacity: buf.readUInt32(),
port: buf.readUInt16()
};
}
Do I have any guarantee that name, capacity, and port will be initialized one after the other? Otherwise, the buffer will be read in the wrong order.
I could of course fall back on:
parsers[1] = function(buf) {
var ret = {type: "init"};
ret.name = buf.readUTF8String();
ret.capacity = buf.readUInt32();
ret.port = buf.readUInt16();
return ret;
}
Thanks to #joews' comment, I can answer my own question.
From the link 11.1.5 Object initializer:
Syntax
ObjectLiteral :
{ }
{ PropertyNameAndValueList }
{ PropertyNameAndValueList , }
PropertyNameAndValueList :
PropertyAssignment
PropertyNameAndValueList , PropertyAssignment
In short, the object constructor takes as arguments either nothing, a list of initialization values, or a list of initialization values followed by a comma.
That list of initialization value is composed by a PropertyAssignment or a list of initialization values followed by a PropertyAssignment, meaning basically a list of PropertyAssignment by recursion.
Now the question is in the last PropertyNameAndValueList , PropertyAssignment, is there a specific order in which both components are evaluated?
The production
PropertyNameAndValueList : PropertyNameAndValueList , PropertyAssignment
is evaluated as follows:
Let obj be the result of evaluating PropertyNameAndValueList.
Let propId be the result of evaluating PropertyAssignment.
...
The order will be guaranteed if 2. is sure to follow 1..
From 5.2 Algorithm conventions:
The specification often uses a numbered list to specify steps in an algorithm. These algorithms are used to precisely specify the required semantics of ECMAScript language constructs. The algorithms are not intended to imply the use of any specific implementation technique. In practice, there may be more efficient algorithms available to implement a given feature.
...
For clarity of expression, algorithm steps may be subdivided into sequential substeps.
So, the expected initialization order is element after element, from what I can gather.
As in many other languages, I would not rely on any "order" of properties of an object nor on the way they are initialized or values are assigned to.
If an "external" order is necessary, I would try to achieve that with a kind of mapping of these properties.

Test JSON object contains the attributes I expect

I am writing a simple nodejs express route that POST's a JSON object.
As a bit of a node/js newbie, I am curious to know if there is an elegant way of testing that a JSON object contains all the attributes that I expect to be submitted and only those attributes?
e.g. if i have a JSON object like this:
data:{
"a":"somevalue".
"b":"somebvalue",
"c":"somecvalue"
}
I was thinking I could do something like:
if( (data.a) && (data.b) && (data.c) ){
//Proceed and process post
else {
// respond with unacceptable
}
Am just wondering if there is a better way, either in JavaScript or express?
Your code is not completely correct for what you're doing. For example for the possible valid object {a:undefined,b:3,c:5} you'd think it does not have the required attributes but in fact it does. A more correct way (assuming the prototype is also fine) would be:
("a" in data) && ("b" in data) && ("c" in data)
If you'd like a solution that scales nicely for multiple properties:
You can use Array.prototype.every:
if(["a","b","c"].every(function(attr){ return attr in data;})){
It's not shorter, but I'd argue it's more semantic, and it doesn't return false positive for empty strings, null and other 'falsy' values. Of course - you can extract this into a function :)
Here's a fiddle

Should I use an empty property key?

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.

Categories