Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse a string into conditions in javaScript?

I have a string containing some conditions, for example:

var str = "this.demoModel.active == '1' && this.demoModel.span > 5 || ..."

Is there a direct way in javascript to parse them so that, they work like a set of conditions. Something like:

if (JSON.parse(str) {}). ??

like image 618
Ashish Ranjan Avatar asked Apr 04 '17 09:04

Ashish Ranjan


4 Answers

In general you should try to avoid getting into this situation at all: storing JavaScript in strings for later evaluation should be avoided if possible. Depending on your actual case you could consider the following option:

1. Use Template Literals:

They are limited in practical use, since they are parsed together with the script in which they are used, but they are also safe:

var str =  `${this.demoModel.active == '1' && this.demoModel.span > 5}`;

When this string is assigned, it will immediately evaluate the ${ } parts in it.

This is thus only a solution if the evaluation can happen immediately, because you cannot store this in a string and then expect to trigger the evaluation later.

And so it is not much different from:

var bool = this.demoModel.active == '1' && this.demoModel.span > 5;

2. Deferred evaluation through callback

A work-around could be to define a function that evaluates the template literal or expression, like so:

var rule = function() { 
    return this.demoModel.active == '1' && this.demoModel.span > 5;
};

... and you can pass that function around, for instance as a callback:

doSomething(rule);

... and then doSomething could call it like this, binding the context, so that this has the appropriate value:

function doSomething(rule) {
    if (rule.call(this)) console.log('rule passed');
}

3. Nested Object Data Structure

Another option would be to create an object structure for the expressions, for example like this:

var rules = [
    [{ // AND conditions:
        "field": "active",
        "compare": "eq",
        "target": 1
    }, {
        "field": "span",
        "compare": "gt",
        "target": 5
    }], // Next array will be OR'ed
    [{
        "field": "...."
        "compare": "..",
        "target": ...
    }]
}];

This is a nested array, where the top level will have rules that must be OR'ed together, while the inner level will be AND'ed together.

Then write a function that will process this structure. The compare names could be mapped to functions in your code, like this:

const comparators = {
    "eq": (a, b) = a === b,
    "gt": (a, b) = a > b
};

So to evaluate one object in the rules array, you could use this:

execute: (obj) => comparators[this.demoModel[obj.compare]] // get the function
    (this.demoModel[obj.field], obj.target) // pass arguments

This rules structure can easily be saved and loaded as JSON string.

like image 55
trincot Avatar answered Nov 16 '22 03:11

trincot


There is, but using them is generally a last resort: eval and the Function constructor. eval runs code from a string input, in the context in which eval is called. The Function constructor creates a function from a string. Naturally, in both cases, this means you must trust the source of the string, since it can contain any arbitrary code and you're happily running it within the context of your code. So you wouldn't, for instance, take a string from User A and then run it in User B's browser — that would leave User B wide open to attack from User A. (For "browser" substitute "session" or whatever, the problem is just as real — perhaps more so — server-side in an environment like Node as it is client-side in a brwoser.)

If you're accepting the string from a user and running it in their own browser/session/context, that's fine.

Here's an example:

function Foo() {
  this.demoModel = {
    active: "1",
    span: 7
  };
}
Foo.prototype.run = function(str) {
  if (eval(str)) {
    console.log("Yes");
  } else {
    console.log("No");
  }
};
var f = new Foo();
f.run("this.demoModel.active == '1' && this.demoModel.span > 5");
f.run("this.demoModel.active == '0' && this.demoModel.span < 5");

You can also use the Function constructor, and then call it with the correct this:

function Foo() {
  this.demoModel = {
    active: "1",
    span: 7
  };
}
Foo.prototype.run = function(str) {
  var testfunc = new Function("return " + str);
  if (testfunc.call(this)) {
    console.log("Yes");
  } else {
    console.log("No");
  }
};
var f = new Foo();
f.run("this.demoModel.active == '1' && this.demoModel.span > 5");
f.run("this.demoModel.active == '0' && this.demoModel.span < 5");

If you have to do this, prefer the Function constructor to eval where possible, since it doesn't have access to everything in scope where you use it, but they both are powerful tools you need to be wary of.

like image 29
T.J. Crowder Avatar answered Nov 16 '22 03:11

T.J. Crowder


You can use 'eval' or 'Function' but as stated on MDN

Don't use eval needlessly! - eval() is a dangerous function, which executes the code it's passed with the privileges of the caller. If you run eval() with a string that could be affected by a malicious party, you may end up running malicious code on the user's machine with the permissions of your webpage / extension. More importantly, third party code can see the scope in which eval() was invoked, which can lead to possible attacks in ways to which the similar Function is not susceptible.

if(new Function("CONDITON_STRING")()){
 //Answer
};
like image 5
LNT Avatar answered Nov 16 '22 02:11

LNT


Try the following:

var str =  'true && true';
var str2 =  'true && false';
function strEval(fn) {
  return new Function('return ' + fn)();
}
var conditionTrue = strEval(str);
var conditionFalse = strEval(str2)
if(conditionTrue)
{
    console.log(conditionTrue)
}
if(!conditionFalse)
{
    console.log(conditionFalse)
}
like image 2
Mamun Avatar answered Nov 16 '22 02:11

Mamun