简体   繁体   中英

Using structured json to determine truthy logic, how can I ultimately return a boolean with using javascript?

Considering the following JSON example of logic, where when you see the value prop it is deriving its value from user input and comparing that against the expression:

{
  "truthy": [
    [
      "AND",
      {
        "operator": "=",
        "compareType": "text",
        "value": "fname",
        "expression": "John"
      },
      {
        "operator": "=",
        "compareType": "text",
        "value": "lname",
        "expression": "Doe"
      }
    ],
    "OR",
    {          
      "operator": "=",
      "compareType": "boolean",
      "value": "Agree",
      "expression": true
    },
    "OR",
    [
      "AND",
      {
        "operator": "=",
        "compareType": "text",
        "value": "fname",
        "expression": "Jane"
      },
      {
        "operator": "=",
        "compareType": "text",
        "value": "lname",
        "expression": "Doe"
      }
    ]
  ]
}

The translated expression would be:

(fname === "John" && lname === "Doe") || agree === true || (fname === "Jane" && lname === "Doe")

Right now what I'm doing is iterating over the structure and pushing values to an array, and later using eval to return true or false .

for (i; i < v.length; i++) {

  if (Array.isArray(v[i])) {
    e.push("(true===");
    e.push(this.parseBlocks(v[i], a, 0));
    e.push(")");
  } else if (v[i] === "OR") {
    e.push("||");
  } else if (v[i] === "AND") {
    e.push("&&");
  } else {
    e.push(true);
    e.push("===");
    e.push(this.parseBlocks([v[i]], a, 0));
  }
}

return eval(e.join(""));

This works great, but was wondering if there would be a more precise way of handling this without building an expression to pass into eval ?

The expression that the above code would generate if first name was John and last name was Doe and the checkbox (ie Agree) was not checked would be:

eval("(true === true) || true === false || (true === false)") 

I didn't need to construct this string to evaluate when it was a simpler expression. For example, the two OR strings in between everything.

The code not included is the actual parsing function, but what it does is takes the first element in the array (ie "AND", "OR") and uses that to determine if that particular block of logic returns true or false.

How could this be handled with the consideration of potential deeper nesting of logic, to determine the outcome without using eval, or considering I'm controlling what's being passed into eval perhaps it may provide better performance in comparison?

Requirements:

  1. I need to keep the current JSON format of the actual conditions as previous functionality relies on it. The inclusion of "AND", "OR", how and where that is placed before, between, after, is optional as I try to find a more concise way of dealing with it.
  2. No, this cannot be done in SQL or other server-side methods

Inspiration:

  1. Found this which has a different structure but supports deeper nesting and complex structure so trying to understand how to adapt my existing solution to handle things similar.

IMO, you need two functions:

  • one to parse the general structure of the json with objects, arrays, ... This is my evaluate
  • one to evaluate the final objects. This is my leafValue

1 - leafValue

This one is the most straightforward. You just need a set of data to evaluate from. Here I created an object called data but in your question you seem to want to get the values from current scope ( fname as opposed to someObject.fname ) so you could just as well replace my data with this .

I then implemented a simple switch / case to potentially account for different operator values. You would need to complicate this a little bit if you also want to implement more complex compareType options, but luckily here, JS is pretty forgiving if we don't use the strict identity === .

In the end, most of the logic is here:

return data[item.value] === item.expression

2 - evaluate

This one is a little trickier. I implemented a depth-first recursive function that will flatten the initial json object iteration by iteration.

  1. Every item that is an Array has to go through evaluate first before it can be useful (depth-first and recursive)

  2. in the order of the array, the first 3 items must always contain 1 combinator ( "OR" or "AND" ) and 2 values (a previous value calculated by evaluate , or the result of an object passed through leafValue ). evaluate finds which is which, and applies the logical operation.

  3. in the array I'm currently evaluating, evaluate replaces the first 3 items with the result of step 2.

  4. Repeat from step 2. If there aren't enough items left, evaluate returns the single value left in the array.


For neatness, I wrapped the both of them in a processData function.

 function processData(data, json) { const combinators = ["OR", "AND"] function leafValue(item) { switch (item.operator) { case '=': return data[item.value] === item.expression } throw new Error(`unknown comparison operator ${item.operator}`) } function evaluate(json) { const evaluated = json.map(item => Array.isArray(item) ? evaluate(item) : item) while (evaluated.length >= 3) { const chunk = [evaluated[0], evaluated[1], evaluated[2]] const combinator = chunk.find(item => combinators.includes(item)) const [A, B] = chunk.filter(item => !combinators.includes(item)) const valueA = typeof A === 'boolean' ? A : leafValue(A) const valueB = typeof B === 'boolean' ? B : leafValue(B) const result = combinator === 'OR' ? (valueA || valueB) : (valueA && valueB) evaluated.splice(0, 3, result) } return evaluated[0] } return evaluate(json) } const json = { "truthy": [ [ "AND", { "operator": "=", "compareType": "text", "value": "fname", "expression": "John" }, { "operator": "=", "compareType": "text", "value": "lname", "expression": "Doe" } ], "OR", { "operator": "=", "compareType": "boolean", "value": "Agree", "expression": true }, "OR", [ "AND", { "operator": "=", "compareType": "text", "value": "fname", "expression": "Jane" }, { "operator": "=", "compareType": "text", "value": "lname", "expression": "Doe" } ] ] } const data = { fname: 'John', lname: 'Doe', } const result = processData(data, json.truthy) console.log(result)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM