Why Won’t eval() Eval My JSON? (Or: JSON Object !== Object Literal)
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.)
Indeed, this ambiguity is irksome. I talked about this ambiguity a bit in the context of automatic semicolon insertion in the comments here:
http://blogs.msdn.com/ericlippert/archive/2004/02/02/66334.aspx
Eric Lippert
March 28, 2007 at 6:23 pm
Ah, thanks Eric! Excellent blog you have there–been going through your posts
rayfd
March 28, 2007 at 6:34 pm
Great Blog!! Thanks!
Siddharth
April 8, 2007 at 11:40 pm
thaaaaanks.. You saved my day, this problem made me crazy for a couple of hours.
Joe
March 20, 2008 at 7:00 am
Thank youuuuuuuuuuuuuu!!!!!!!!!!!!!
Narine
April 18, 2008 at 5:26 am
Excellent! Thanks
Wole
May 4, 2008 at 11:30 pm
You just saved my day! Been messing with the “missing ; before statement” bug for half a day, finding this solved it. Thanks a bunch!
casperbp
May 5, 2008 at 1:53 pm
[...] funkcji eval() do parsowania obiektu JSON który otrzymałem z API otrzymywałem Parse Error. Wyjaśnienie tego problemu znalazłem, ale zajęło mi to dość dużo czasu, zatem warto się nim podzielić [...]
//DEVGURU » Dlaczego JSON Object + eval() = Parse Error?
June 3, 2008 at 10:53 am
THANK YOU
MAnuel
June 5, 2008 at 4:19 pm
Nice Introductory Article. Inspired by this article I wrote a fully functiional PHP-JSON-Ajax Demo
Please have a look
http://www.codemator.com/2008/10/19/using-php-and-json-for-ajax-response/
codemator
October 19, 2008 at 7:11 pm
Thanks for taking the time to post this – I had been chasing the same problem for hours
Stuart Crsobie
November 15, 2008 at 1:42 pm
Yeah, it’s strict when parsing JSON data. you got to make sure the JSON data is well formatted. May touch up a bit when using eval()
http://en.wikipedia.org/wiki/JSON
Boris
January 8, 2009 at 9:21 am
Its good discussion I too was struggling found a solution here
http://www.isolutionteam.co.uk/json-jquery-ajax-aspnet-and-c-to-get-ajaxed-data-table-rows-passing-multiple-parameters/
naveedkamboh
February 13, 2009 at 12:24 pm
Thanks — this was driving my crazy — the parens worked great.
CMS
June 4, 2009 at 2:05 pm
Awesome tip! Works like a charm
michael
July 10, 2009 at 4:29 pm
Thats a cool analysis, i have been through it and i know how irritating it is to use eval() even strange “{‘name’:'tom’}” doesnt work for JSON.parse() looks like json parser looks for literals after literals and not use the eval() mehthod at all.
sammy cool
February 3, 2010 at 4:37 pm
I’m glad someone had the patience to go through the spec… I tried your suggestion to wrap the object in brackets and it worked perfectly. Thanks!
Tim Farland
July 27, 2010 at 9:26 am
if look the Douglas Crockford’s JSON JavaScript library, at the bottom you will see the line j = eval(‘(‘ + text + ‘)’);
Every javascript library with JSON support, uses the eval function.
anon
August 27, 2010 at 12:18 am
Thanks a lot, i’ve been rackin’ my brain with this problem for 2 days. Thanks for reading the spec!, I think I wouldn’t have tried it by myself :p
Juan Capristán
October 15, 2010 at 9:57 am
Thanks Curious Schemer!
This was a very helpful bit of advice. Appreciate your having taken the time to write it up and share it with the rest of us. -Robert
Robert
October 17, 2010 at 3:13 am
How to pass a json object to other javascript file…
Munish
May 27, 2011 at 8:29 am
i am new to birt(business intelligence and reporting technique)used in eclipse… how can i bind json data with birt chart or report.. i tried alot to search the same on net but dint get anything… all examples are of high level.. i need simple example of binding json with birt chart…….. see can u help me out
Munish
May 27, 2011 at 11:14 am
Thanks so much, that solved that little headache
Adams
June 13, 2011 at 10:26 pm
[...] PHP Cross-Site Page Access, http://rayfd.wordpress.com/2007/03/28/why-wont-eval-eval-my-json-or-json-object-object-literal/ JS Eval string to JSON Object, [...]
Serialize C# object to JSON JavaScriptSerializer .Net « Fraction of the Blogosphere
November 29, 2011 at 4:30 pm
Thank you! There are some times I hate JavaScript…
Anonymous
March 30, 2012 at 9:42 am
does eval() works fine in IE6 too ??
dinker
March 4, 2013 at 7:34 pm