This question already has answers here:
Why does JavaScript's eval need parentheses to eval JSON data?
(7 answers)
Closed 7 years ago.
If I run this:
eval('{ear: {"<=": 6}}');
I get an error:
Uncaught SyntaxError: Unexpected token :
Let's create the object manually:
var foo = {};
foo.ear = {};
foo.ear["<="] = 6;
Now, the following code:
JSON.stringify(foo)
Returns the following string:
'{"ear":{"<=":6}}'
The same string as the one I started with (except the white characters, but those are irrelevant), so eval(JSON.stringify(foo)) returns the same syntax error error message. However:
$.parseJSON(JSON.stringify(foo))
is executed correctly. What is the reason of that?
EDIT:
As nnnnnn and Ron Dadon pointed out, the initial string and the result of stringify are different. However, as I pointed out in the question, even the result of stringify used as input for eval will result in the syntax error message.
EDIT2:
Based on the answers and experiments conducted, this function is interesting:
function evalJSON(text) {
return eval("(" + text + ")");
}
Main {} are parsed as block statement.
try to wrap in parenthesis:
eval('({ear: {"<=": 6}})');
In javascript {} can be parsed as a block or an object
examples:
//object
var user = {
name: "John",
age: "32"
};
//block
{
let a = 5;
console.log(a);
}
//object:
var a = {};
console.log({});
return {};
({});
//block:
function(){}
for(k in o){}
{}
Object literal notations need to be evaluated. This happens when you assign a variable:
var a = {ear: {"<=": 6}};
or when you put parentheses around it, a anonymous object:
({ear: {"<=": 6}});
Otherwise curly brackets are parsed as block markers. In your case this means {ear:...} is a label definition, the label is named ear. The next block {"<=": 6} gives you a syntax error because "<=": 6 is invalid syntax.
The same applies if you put this into an eval statement.
It's not flawed. To understand what is happening you need to understand what kind of statements are seen (left to right) by the parser.
A simple way to get into it is to play around with a Javascript AST Visualizer
You will get the same exception with much simpler {"b":4}. It's parsed as "b":4 inside a block. That's not valid javascript. No AST tree for you...
However it's due to an exception inside of a {} statement. That's a BlockStatement. AST tree:
A similar {b:4} would be understood as b:4, a valid js statement - a b label for 4... That's parsed as
Lastly, a ({b:4}) would be understood as an object declaration with a b property equal to 4. That's parsed as
ECMAScript 2015
On Blocks:
Block : { StatementList }
On eval itself:
Eval creates a new Realm, which is parsed (several steps here) as a sequence of Statements ( a StatementList ), which in turn this section has BlockStatement as a first option. This must start with { (see above), so if you wrap it with a bracket (({})) it cannot be a BlockStatement... But if it matches as BlockStatement it must be a BlockStatement.
A side note in section on Expressions:
An ExpressionStatement cannot start with a U+007B (LEFT CURLY BRACKET) because that might make it ambiguous with a Block
Related
This question already has answers here:
What are the rules for JavaScript's automatic semicolon insertion (ASI)?
(7 answers)
Closed 1 year ago.
Why does it happen that the below code throws the error Uncaught ReferenceError: Cannot access 'arr' before initialization
function swap(str,a,b){
let arr = str.split("")
[arr[a],arr[b]] = [arr[b],arr[a]]
return arr.join("")
}
swap("abcde",2,4) // throws error "Uncaught ReferenceError: Cannot access 'arr' before initialization"
But as soon as I insert any dummy statement between line 2 and 3, surprisingly code starts to work
function swap(str,a,b){
let arr = str.split("")
23456; // this could be anything like a variable declaration, or any valid javascript statement
[arr[a],arr[b]] = [arr[b],arr[a]]
return arr.join("")
}
swap("abcde",2,4) // returns "abedc" as expected
I am surprised to see why does this even work now ? It should have given error in both the cases, or should have worked in both cases.
I'm sure that in the 1st case, split is not finishing it's work till the time line 3 is executed and in 2nd case, it is getting enough time because of 1 extra statement in between.
But I would still love to get a clear explanation as to why it is behaving like this.
I'm not sure if this is same for all browsers, but I've tried it on Chrome and Safari on mac, and both of them gave same error.
The semicolon is making the difference. JavaScript thinks you want to access the result of str.split("")[arr[a],arr[b]] like an array. This is why JavaScript autoformatters will insert a semicolon at the beginning of the next line like this:
function swap(str,a,b){
let arr = str.split("")
;[arr[a],arr[b]] = [arr[b],arr[a]] // <--- semicolon in front of this line
return arr.join("")
}
I think your syntax is off a bit
split will return an array and trailing it with brackets is translated as if you are trying to access an element of the array I believe you are missing the semi colon only
let arr = str.split("")[arr[a],arr[b]] = [arr[b],arr[a]]
function swap(str,a,b){
let arr = str.split("");
[arr[a],arr[b]] = [arr[b],arr[a]]
return arr.join("")
}
console.log(swap('some tpyo', 6, 7))
I have searched but could not find logic behind following in JavaScript.
When I type in the Chrome console:
{} == null
it returns
Uncaught SyntaxError: Unexpected token ==
But
{} == {}
and
{} == function(){}
returns false
Why?
I assume you understand why {} == null throws SyntaxError. Long story short it is because { in the begining starting a block statement not an object literal. You could check the answer here
As of why {} == {} this works.
If you check chromium code that evaluates expressions in console. You could find the following (code)
if (/^\s*\{/.test(text) && /\}\s*$/.test(text))
text = '(' + text + ')';
executionContext.evaluate(text, "console", !!useCommandLineAPI, false, false, true, printResult);
This code wraps {} == {} code with parentheses making it a valid expression ({} == {}) comparing two empty object literals. Which evaluates to false because objects are compared by reference.
Node repl has the same behaviour src
if (/^\s*\{/.test(code) && /\}\s*$/.test(code)) {
// It's confusing for `{ a : 1 }` to be interpreted as a block
// statement rather than an object literal. So, we first try
// to wrap it in parentheses, so that it will be interpreted as
// an expression.
code = `(${code.trim()})\n`;
wrappedCmd = true;
}
You can find this in the specs under Statement (art. 12)
12 - Statement
Statement :
Block.
VariableStatement
EmptyStatement
ExpressionStatement
.
.
.
The first applicable rules are either Block or Expression Statement. So we need to look at 12.4.
In 12.4 the specs clearly state that an expression statement cannot start with a {.
though i haven’t yet found what makes example 2 an expression, maybe it’s implementation specific
12.4 Expression Statement
Syntax
ExpressionStatement :
[lookahead ∉ {{, function}] Expression ;
NOTE An ExpressionStatement cannot start with an opening curly brace because that might make it ambiguous with a Block. Also, an ExpressionStatement cannot start with the function keyword because that might make it ambiguous with a FunctionDeclaration.
Semantics
The production ExpressionStatement : [lookahead ∉ {{, function}]Expression; is evaluated as follows:
Let exprRef be the result of evaluating Expression.
Return (normal, GetValue(exprRef), empty).
I would say this is a parsing issue, rather than a logic issue per se.
In Chrome I get the observed behavior.
In IE I get syntax errors whenever I put {} (or, it seems, any object literal) on the LHS of the ==.
In both browsers, putting () around the expression fixed things. Or first assigning the object to a variable
var x = {}
x == null
I would say it seems to me like a bug in the parsing. Whether that is true in an academic sense would take digging through specs and grammars; the practical answer is, there are simple enough work-arounds that the best bet is to not do that.
It’s because JavaScript sucks
The reason it doesn’t work is because
JavaScript takes the type the comparison is taking from the first object.
For instance
3+”1” = 4
But
“3”+1 = 31
The first example does the operation as a number because the first object is a number
The second example sees a string as the first object and treats the operation as concatenation of a string.
For your example
{} is an object but null can’t be converted to an object
It works the other way because
{} can be represented as a null object.
Sorry for my ignorance on JavaScript basic concepts.
It boils down to this:
Literal - A value found directly in the script. Examples:
3.14
"This is a string"
[2, 4, 6]
Expression - A group of tokens, often literals or identifiers, combined
with operators that can be evaluated to a specific value. Examples:
2.0
"This is a string"
(x + 2) * 4
There is a very clear difference b/w the above two in Javascript.
I happen to read this article. And I am familiar with the difference b/w function declaration & function expression and when to use one over other or vice-versa.
From the same article:
....You might also recall that when evaluating JSON with eval, the string
is usually wrapped with parenthesis — eval('(' + json + ')'). This is
of course done for the same reason — grouping operator, which
parenthesis are, forces JSON brackets to be parsed as expression
rather than as a block:
try {
{ "x": 5 }; // "{" and "}" are parsed as a block
} catch(err) {
// SyntaxError
}
({ "x": 5 }); // grouping operator forces "{" and "}" to be parsed as object literal
So, what difference does it make to parse something as an object literal other than parsing them as a block?
And for what purpose should I consider to make use of grouping character, in context of parsing?
First, don't eval JSON, use JSON.parse on the String source
A block is a "group of expressions" for example,
let x = 0;
if (true) {
// this is a block
++x;
}
However, equally this is also a block
let x = 0;
{ // hi there, I'm a block!
++x;
}
This means when the interpreter sees block notation, it assumes a block even if you do something like this
{ // this is treated as a block
foo: ++x
}
Here, foo acts as a label rather than a property name and if you try to do more complex things with the attempted object literal, you're going to get a Syntax Error.
When you want to write an Object literal ambiguously like this, the solution is to force the interpreter into "expression mode" explicitly by providing parenthesis
({ // this is definately an Object literal
foo: ++x
})
A group that begins with a { and ends with a } is treated as either object literal or a block depending on context*.
Within an expression context the group is interpreted as an object literal. Writing a block within an expression context will generate a syntax error:
// Valid code:
foo = {a:b};
({a:b});
// Syntax errors:
foo = {var a = b};
({var a = b});
Outside of an expression context the group is interpreted as a block. Depending on exactly how the code is written, an object literal written outside of an expression context is either a syntax error or will be interpreted as a label.
*note: In the ECMAscript spec the word "context" is used to mean something specific. My use of the word here is with the general meaning in computer science with regards to parsing.
May need a Javascript language lawyer for this one:
var s1 = "{\"x\":\"y:z\"}"
var o = JSON.parse(s1)
var s2 = JSON.stringify(o)
$('span#s').text(s1);
$('span#s2').text(s2);
if (s1 === s2) {
$('span#areEqual').text('s1 === s2')
} else {
$('span#areEqual').text('s1 !== s2')
}
JSON.parse(s2) // okay
$('span#jsonParse').text("JSON.parse(s2) okay")
eval(s2) // bad mojo!
$('span#eval').text("eval(s2) okay")
eval("("+s2+")") // bad mojo, too!
$('span#eval2').text("eval((s2)) okay")
eval fails on s1, s2, and "("+s2+")".
jsFiddle here.
Your problem is that you mixing two unrelated things.
eval() is built-in javascript function, which main purpose is to interpret string of javascript code (thus make potentional security hole)
JSON.parse() function is for parse JSON string. Although very simmilar, do not make mistake, JSON is not Javascript and there are tiny differences. You should not use eval() for parsing JSON
What are the differences between JSON and JavaScript object?
$eval is automatically evaluated against a given scope.
For example:
$scope.a = 2;
var result = $scope.$eval('1+1+a');
// result is 4
$parse does not require scope. It takes an expression as a parameter and returns a function. The function can be invoked with an object that can resolve the locals:
For example:
var fn = $parse('1+1+a');
var result = fn({ a: 2 });
// result is 4
When you use eval for parsing JSON you need to wrap your expression with parentheses
eval('(' + s2 + ')');
jsfiddle
Check out what the specification says about JSON and eval
http://www.json.org/js.html
Notice this part specifically
The eval function is very fast. However, it can compile and execute
any JavaScript program, so there can be security issues. The use of
eval is indicated when the source is trusted and competent. It is much
safer to use a JSON parser. In web applications over XMLHttpRequest,
communication is permitted only to the same origin that provide that
page, so it is trusted. But it might not be competent. If the server
is not rigorous in its JSON encoding, or if it does not scrupulously
validate all of its inputs, then it could deliver invalid JSON text
that could be carrying dangerous script. The eval function would
execute the script, unleashing its malice.
JSON is just a javascript object, and nothing more. Valid javascript could include functions, execution blocks, etc. If you just eval() a string, it could have code it in. JSON will parse if it's just JSON, but you can't know for sure by just stuffing it into eval. For example
var s = "(function(){ /* do badStuff */ return {s: 123, t: 456}; })()";
var result = eval(s);
Would give you a var result with the contents {s: 123, t: 456} but would also execute any code hidden in your function. If you were taking this input from elsewhere, code could be executing and not actually break anything on your end. Now the same example with JSON.parse
var result = JSON.parse(s);
It throws an error with the message:
Uncaught SyntaxError: Unexpected token (
So the parse saves you from remote code execution here, even if someone tried to sneak it in.
eval wasn't an expression - i've updated it to evaluate eval(s2 === s1);
Otherwise it will try & execute what's within the eval & stop execution.
eval() attempts to evaluate a block of JavaScript code. If you had created a script file that started with the same text, you would have gotten the same error. In that context, I believe the braces signify a compound statement, as in an if-statement or for-statement body, but at the beginning of the compound statement is a string followed by a colon, which is not valid syntax.
If you wanted a string that would evaluate to an object, you'd have to enclose the object expression in parentheses to make it explicit that it's an expression. But as apocalypz says, you should not attempt to eval JSON. It's wrong on so many levels.
if you really want to use eval instead of JSON.parse() for parsing JSON then you should write something like
var o2; // declare o2 out of eval in case of "use strict"
eval("o2 = "+s1); // parse s1 and the assignment to the local o2
console.log(o2); // enjoy the local variable :)
...
In the console of both FF and Chrome, {} is considered undefined until explicitly evaluated:
{}; // undefined
({}); // ▶ Object
Actually, it's a bit less defined than undefined -- it's apparently bad syntax:
{} === undefined; // SyntaxError: Unexpected token ===
{}.constructor; // SyntaxError: Unexpected token .
But not if it's on the other side, in which case it's fine:
"[object Object]" == {}.toString(); // true
Or if it's not the first expression:
undefined + undefined; // NaN
{} + undefined; // NaN
undefined + {}; // "undefined[object Object]"
What gives?
If you use the curly brackets by themselves, it's not an object literal, it's a code block. As the code block doesn't contain any code, evaluating it results in undefined.
Okay, here is my answer. There is nothing new here. I am just linking to (a pretty copy of) the ECMAScript specification for the grammar and showing a few productions to show "why" it parses the way it does. In any case, the behavior is well-defined according to the JavaScript/ECMAScript grammar rules: {} is parsed differently depending upon the "context" it is in.
The JavaScript REPLs ("consoles") start to parse the code in the Statement grammar production or "statement context". (This is actually a lie, it starts at the Program or SourceElements production, but that adds additional constructs to dig through.) Here is a rough grammar breakdown with simplifications and omissions; see the link above for more:
Statement
Block
...
ExpressionStatement
Block
# This is actually { StatementList[optional] }, but this is what
# it amounts to: * means "0 or more".
{ Statement* }
ExpressionStatement
# An ExpressionStatement can't start with "{" or "function" as
# "{" starts a Block and "function" starts a FunctionStatement.
[lookahead ∉ {{, function}]Expression ;
Expression
# This is really PrimaryExpression; I skipped a few steps.
...
( Expression )
Thus (when in "statement context"):
{}
-> Block # with no StatementList (or "0 statements")
-> Statement
And:
({})
-> (Expression)
-> Expression
-> ExpressionStatement # omitted in productions below
-> Statement
This also explains why undefined === {} parses as EXPR === EXPR -> EXPR -> STMT and results in false when evaluated. The {} in this case is in an "expression context".
In the case of {} === undefined it is parsed as {}; === undefined, or BLOCK; BOGUS -> STMT; BOGUS, which is a Syntax Error. However, with the addition of parenthesis this changes: ({} === undefined) is parsed as (EXPR === EXPR) -> (EXPR) -> EXPR -> STMT.
In the case of {} + "hi" it is parsed as {}; + "hi", or BLOCK; + EXPR -> STMT; EXPR -> STMT; STMT, which is valid syntax even though it is silly (+ is unary in this case). Likewise, just as above, "hi" + {} puts the {} into an "expression context" and it is parsed as EXPR + EXPR -> EXPR -> STMT.
The JavaScript console is just showing the result of the last Statement, which is "undefined" (well, "nothing" really, but that doesn't exist) for an empty {} block. (This might vary between browsers/environments as to what is returned in this case, e.g. last ExpressionStatement only?)
Happy coding.
If you just type {} as input in any console, there is no context to interpret what you want the curly braces to mean, other than it's position. given the fact each input to the console is interpreted as fresh line of code, the opening curly brace is seen as the start of a new block. The closing } is syntactically correct, since an empty block is often used in situations like these:
try
{
//something
}
catch(e)
{}//suppress error
Hence {} will always be undefined when it is on the left hand side, and never spit errors as an empty block is valid code.
It seems like both consoles treat it as an ambiguous condition when the expression starts with {. Maybe it is treated as a dummy block.
Try this:
{} // undefined
undefined === {} // false
Using {} as a right-hand-expression removes the ambiguity.
Also you can see from:
{a:42} // 42
{{a:42}} // 42
{{{a:42}}} // 42
That the outer braces are really treated as a dummy block.
And this doesn't seem to be a console feature. Even eval treats them like that, hinting to the fact that the stuff you type in the console actually get evaluated the same way they would when passed to eval:
eval("{}") // undefined
eval("{alert(42)}") // alerts 42
The problem is that in some cases, javascript sees { and } as opening and closing a /block/. While in other cases, {} is an object. The cases really depend on the context.
Doug Crockford complains about this. That WTF you're getting there is due to the + operator itself. It's used to both to arithmetic and concatenate. In your last line there, you're seeing the + operator convert undefined and the empty object to strings, and concatenating them.
Javascript separates the concept of statements and expressions (languages like C++ or Java do the same).
For example if ... is a statement, and x?y:z is an expression.
Expressions have a value, statements do not.
One problem with Javascript syntax is that {} can be either an expression (and in this case it means an empty object constructor) or a statement (and in this case it means an empty code block... basically a NOP) so how it's interpreted depends on the context.