I’ve been reviewing a lot of code lately, and I found so many abuses of eval(), that I wanted to blog about it. But I found that Eric Lippert has done excellent short articles about it right here, and here, and the abuses he blogged about there are exactly the ones I keep seeing in the code I’ve been reviewing. So instead, I’ll blog about something that baffled me a few months back, when eval() simply wouldn’t work when I used it to evaluate my JSON string.

The scenario was simple: we were getting back JSON strings that represent complex objects from the server side. Most of them were arrays that contained objects, and it worked perfectly, like demonstrated in my favourite tool jrunscript below:

js> var jsonEmployees = "[ { name: 'Ray', role: 'Fixer' }, { name: 'Rayzor', role: 'Cutter' } ]";

js> var employees = eval(jsonEmployees);

js> employees.length

2.0

js> employees[0].name

Ray

js> employees[0].role

Fixer

However when the JSON string is not an array, but an object, like this, it barfs:

js> var jsonEmployee = "{ name: 'Ray', role: 'Fixer' }";

js> var employee = eval(jsonEmployee);

script error: sun.org.mozilla.javascript.internal.EcmaError: SyntaxError: missing ; before statement (<STDIN>#1(eval)#1) in <STDIN>#1(eval) at line number 1

What happened? It says it’s missing a “;”. So maybe we need a ; at the end of the object literal:

js> var jsonEmployee = "{ name: 'Ray', role: 'Fixer' };";

js> var employee = eval(jsonEmployee);

script error: sun.org.mozilla.javascript.internal.EcmaError: SyntaxError: missing ; before statement (<STDIN>#1(eval)#1) in <STDIN>#1(eval) at line number 1

Hmmm. Obviously not. But why? I checked and rechecked the JSON syntax in the JSON site, it seemed to be correct. Putting the object inside an array (i.e.: “[{ name: ‘Ray’, role: ‘Fixer’ }]”) always works, but why shouldn’t eval() eval what seems to be a perfectly valid JSON syntax? (Actually, strictly speaking, no, it is not a valid JSON syntax although it is valid JavaScript syntax–I realized this later when I incorporated json.js into my app. But that’s not why eval() failed. Keep on reading.)

Ugh, Spec! Expressions! Ambiguity!

Have you ever read a language spec? I hate language specs (especially C++’s). But the answer to our question is buried deep somewhere inside the ECMAScript standard, 3rd edition, section 12.4, about Expression Statement: “Note that an ExpressionStatement cannot start with an opening curly brace because that might make it ambiguous with a Block.”

Ahhh, so when we do this:



js> var jsonEmployee = "{ name: 'Ray', role: 'Fixer' };";

eval() gets confused, because it is interpreting the “{” as the beginning of a block instead of an object literal! Fortunately, there are a few ways to get around this. The easiest one is to add parentheses around the expression. That is:

js> var jsonEmployee = "({ name: 'Ray', role: 'Fixer' })";

js> var employee = eval(jsonEmployee);

js> employee.name + ", " + employee.role

Ray, Fixer

Alternatively, we can make it obvious to eval() that the expression is an object initializer, by assigning it to a variable within the eval string:

js> var jsonEmployee = "var employee = { name: 'Ray', role: 'Fixer' };";

js> eval(jsonEmployee);

js> employee.name + ", " + employee.role

Ray, Fixer

A Better But Picky eval() That Won’t Evaluate My Object Literals

eval() works well when used appropriately. For instance, for the case of my app, using eval() is fine, not evil, because we’re only using it to parse JSON strings that come from a trusted server (that is, our own server). It’s different when the string you’re eval()-ing (JSON or otherwise) comes from some untrusted source. In that case, it’s better to use a real JSON parser.

(Remember that JSON is a subset of JavaScript–a JSON parser only parses JSON, and not the rest of JavaScript. Whereas eval() parses everything. That said, JSON parser is obviously slower since it has to check that only valid JSON can be eval()-ed.)

Using the JSON parser is simple. You don’t even need to add parentheses to get the string parsed. Just download Douglas Crockford’s JSON JavaScript library here, include it in your page (or load it if you’re running in jrunscript), and you’re set.

Note something about this parser though: it is REALLY strict. It won’t parse perfectly OK object literals like { name: “Ray”, age: 31 }. Eval parses this without problems:

js> var jsonRay = "{ name: 'Ray', age: 31 }";

js> var ray = eval("(" + jsonRay + ")");

js> ray.name

Ray

js> ray.age

31.0

But when we use the JSON library, only JSON-compliant strings are accepted. Let’s try it using jrunscript:

js> load("C:\\Documents and Settings\\schemer\\My Documents\\json.js")

js> var jsonRay = "{ name: 'Ray', age: 31 }";

js> var ray = jsonRay.parseJSON();

script error: sun.org.mozilla.javascript.internal.JavaScriptException: [object Error] (C:\Documents and Settings\schemer\My Documents\json.js#270) in C:\Documents and Settings\schemer\My Documents\json.js at line number 270

This string: “{ name: ‘Ray’, age: 31 }” is not valid JSON! Check the specification again. An object has to look like this to be JSON-compliant (properties names have to be quoted, and with double quotations marks too):

{ "name": "Ray", "age": 31 }

And indeed, now the string is parsed correctly into an object. Note that unlike eval(), we don’t need to put parentheses around the object initialiser–the parseJSON() method does it for us:

js> var jsonRay = "{ \"name\": \"Ray\", \"age\": 31 }";

js> var ray = jsonRay.parseJSON();

js> ray.name

Ray

js> ray.age

31.0

The fact that the new json.js adds parseJSON() as a method of String (instead of the old approach of passing the string to JSON.parse()) changes the way you code, because obviously you can’t call parseJSON() on a null or undefined variable. This JSON library also adds toJSONString() method to JavaScript datatypes, so we can do this:

js> ray.toJSONString()

{"age":31,"name":"Ray"}

The Conclusion

Use eval() when you can trust the string. Use parseJSON() otherwise. (I know, I know, it’s a lousy conclusion, like a fizzle after a pop or something. But I’m getting real sleepy, kay? Need to get some sleep.)