10

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) {}). ??

12
  • 3
    @musefan use of eval should be discouraged. Commented Apr 4, 2017 at 9:25
  • 1
    Rethink your logic. It is bad practice to store code in strings to evaluate them later. Commented Apr 4, 2017 at 9:25
  • 1
    @SagarV: I don't need you to explain the dangers to me. I want you to back up your statements. There are perfectly valid situation where it can be used correctly, so it isn't right to just blindly say "dont use it". You don't even know the full situation of the OP so how can you be sure it is wrong? Commented Apr 4, 2017 at 9:30
  • 1
    @AshishRanjan Where do these Strings come from? Why do you need to evaluate strings instead of running real code? Commented Apr 4, 2017 at 10:20
  • 1
    @musefan Because he is not experienced enough to even know eval and the Function constructor, so how do you assume he'd know the possible implications/dangers of using them? That's why he should be discouraged to use them and rather look for a different approach to his problem. It's decades of experiance in the community why eval is generally frowned upon. Commented Apr 4, 2017 at 10:21

5 Answers 5

18

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.

Sign up to request clarification or add additional context in comments.

11 Comments

I really thought that the Template literals is the right solution. But in my case, the value of the model could change after some time, and since from what I have observed after trying it out, the Template literal is evaluated only once and results to the same old evaluation even if the data has changed. The JSON implementation could get complex, but I will try it out. Thanks :)
Indeed, like I wrote, template literals are evaluated and resolved immediately, at the moment they are encountered. For deferred execution, you'll need another solution. JSON is very flexible, you just have to define what you allow to happen, and what not. This way you are sure your code will never execute something that you really don't want.
Just an afterthought. If you have the template literal inside a function, you can always execute that function again, and then the template literal will also be evaluated again.
The template literal is present in a different class A which passes the string to function of a different class B (the evaluation should be done in class B) note: there could be multiple caller classes for the function in class B.
thanks for suggesting point 3, I went for that solution and in the end it's not much more work than pure eval, while it significantly improves readibility and safety
|
9

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.

1 Comment

Thank you for this wonderful explanation! I need to rethink if I really need this. From what you have explained, I think it is okay to use eval or function constructor in my case. I am using angular 2 and making a service which runs in a loop and stops when the passed condition from the caller satisfies. I am having trouble with the this context but I will figure it out and will try to understand more of the consequences before giving the final implementation.
5

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
};

Comments

2

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)
}

Comments

-1

Use eval method to convert a string to command.

var cond = 'd === 1';
var d = 0;

if (eval(cond)) { console.log('1'); } else { console.log('2'); }    // output: 2
d = 1;
if (eval(cond)) { console.log('1'); } else { console.log('2'); }    // output: 1

1 Comment

Suggesting eval without warning of its dangers is a Bad Idea™.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.