create dynamic ifs from a "query" string

Viewed 33

i will try to explain this my best.

  1. I have a data array from many many registers to work on

  2. 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!

1 Answers

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

  1. 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.

  2. 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.

  3. 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