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

Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
Ashish Ranjan
  • 12,760
  • 5
  • 27
  • 51
  • check out the `eval` function – musefan Apr 04 '17 at 09:24
  • 3
    @musefan use of `eval` should be discouraged. – Sagar V Apr 04 '17 at 09:25
  • @SagarV: Why should it? – musefan Apr 04 '17 at 09:25
  • 1
    Rethink your logic. It is bad practice to store code in strings to evaluate them later. – trincot Apr 04 '17 at 09:25
  • @musefan I suggest this http://stackoverflow.com/questions/197769/when-is-javascripts-eval-not-evil – Sagar V Apr 04 '17 at 09:28
  • 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? – musefan Apr 04 '17 at 09:30
  • @trincot: Depends if you're the type of person to jump to incorrect assumptions... fact is, I *want* them to explain why it should be discouraged. So that's what I said. I am not going to try and manipulate a person with sugar coated words like "I believe it would be of great benefit to the community if you explain why it should be discouraged" just to improve the chances of them doing so. They either will, or they won't, doesn't much matter either way. – musefan Apr 04 '17 at 09:47
  • 1
    @AshishRanjan Where do these Strings come from? Why do you need to evaluate strings instead of running real code? – Thomas Apr 04 '17 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. – Thomas Apr 04 '17 at 10:21
  • 1
    @Thomas: Well as a developer it's his job to learn what he chooses to use. Let's say the requirement is to allow a user (a site admin for example) to enter custom evaluation conditions in a textbox. How would you suggest that is solved? Why assume all sites are customer facing and open to potential hackers anyway... – musefan Apr 04 '17 at 10:43
  • @Thomas: I am actually doing an angular2 implementation, making a service which keeps doing an HTTP request at set intervals and stops when a certain condition is met. This `condition` is dependent upon the caller and may vary from caller to caller. – Ashish Ranjan Apr 04 '17 at 11:00
  • 1
    So why do these conditions need to be strings? You could write them as distinct functions and choose wich function you use for that particular caller. – Thomas Apr 04 '17 at 11:08

5 Answers5

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.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • 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 :) – Ashish Ranjan Apr 04 '17 at 10:46
  • 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. – trincot Apr 04 '17 at 11:00
  • 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. – trincot Apr 04 '17 at 11:03
  • 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. – Ashish Ranjan Apr 04 '17 at 11:12
  • Wonderful!! :) Just one thing, when the conditions resolve, they still show like: `1 == '0'`. So something like this is required: `if(eval(rule.call(this)))`. – Ashish Ranjan Apr 04 '17 at 12:14
  • Can you give an example? It probably means your template literal is not designed well. Anyway, the goal is to avoid `eval`. – trincot Apr 04 '17 at 12:22
  • The template string looks something like this: ``${this.objectA.objectB.active} == '1'`` – Ashish Ranjan Apr 04 '17 at 12:28
  • Make it `${this.objectA.objectB.active == '1'}` – trincot Apr 04 '17 at 12:32
  • In fact, it seems you don't need a template literal at all for your case. I added a new point 2 in my answer between the two other points, where you just defer the evaluation, but don't need a string nor template literal. – trincot Apr 04 '17 at 12:38
  • Thank you so much for walking me through all this. This solution works for me!! :) – Ashish Ranjan Apr 04 '17 at 12:40
  • 2
    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 – kurdemol94 Jul 08 '21 at 16:57
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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 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. – Ashish Ranjan Apr 04 '17 at 10:41
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
};
trincot
  • 317,000
  • 35
  • 244
  • 286
LNT
  • 876
  • 8
  • 18
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)
}
Mamun
  • 66,969
  • 9
  • 47
  • 59
0

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
Matansh
  • 764
  • 4
  • 13