From: Garrett Smith on
The FAQ mentions JSON, but only for "when do I use eval". That entry is
not focused on a specific task.

An FAQ entry JSON, in contrast, would be focused on a specific task, and
a common one. It would be useful to mention JSON.parse support there.

FAQ Entry Proposal:

| How do I evaluate a JSON response?
|
| An XMLHttpRequest's responseText can be evaluated in a few ways. The
| Function constructor and eval are both widely supported; either can
| be used to evaluate trusted code.
|
| The Function constructor creates a globally-scoped Function. In
| contrast, eval runs in the calling context's scope.
|
| To evaluate code with the Function constructor, you could use:
|
| function evalResponse(responseText) {
| return new Function("return(" + responseText + ");")();
| }
|
| Where supported, JSON.parse may be used.
|
| var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
| typeof JSON.parse === 'function' &&
| JSON.parse('true').test;
|
| function evalResponse(responseText) {
| if(NATIVE_JSON_PARSE_SUPPORT) {
| try {
| return JSON.parse(responseText);
| } catch(ex) {
| return "Error";
| }
| } else {
| return new Function("return(" + responseText + ")")();
| }
| }
|
| If the argument to JSON.parse is not JSON, a SyntaxError will be
| thrown.

Garrett
From: Asen Bozhilov on
Garrett Smith wrote:

> | In contrast, eval runs in the calling context's scope.

As you know, that is not true in ECMA-262-5 strict mode. And `eval' is
not run in calling execution context. ECMA-262-3 define `10.1.2 Types
of Executable Code` and eval code is a part of that section. So eval
code is running at separate execution context. That execution context
use the same Variable Object and `this` value as calling execution
context.


> | Where supported, JSON.parse may be used.
> |
> | var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
> |   typeof JSON.parse === 'function' &&
> |   JSON.parse('true').test;

window.JSON ? Why do you use `window'? At environment where Global
Object does not have property `window' or refer host object your code
has two options:

- ReferenceError `window' is not defined
- Result of expression is evaluated to `false'

> | function evalResponse(responseText) {
> |   if(NATIVE_JSON_PARSE_SUPPORT) {
> |     try {
> |       return JSON.parse(responseText);
> |     } catch(ex) {
> |       return "Error";
> |     }
> |   } else {
> |     return new Function("return(" + responseText + ")")();
> |   }
> | }

What do you think about the follow string:

'{JSON : false}'

By JSON syntax grammar that is not syntactical valid. So if I run your
code in implementation where is presented built-in `JSON' I will have
result "Error". If I run the code in implementation without `JSON'
that string will be evaluated as well because is bound by syntax rules
of `11.1.5 Object Initialiser`. Think about it ;~)




From: Thomas 'PointedEars' Lahn on
Garrett Smith wrote:

> | var NATIVE_JSON_PARSE_SUPPORT = window.JSON &&
> | typeof JSON.parse === 'function' &&
> | JSON.parse('true').test;

Don't. `JSON' is supposed to be a built-in object in conforming
implementations, so the name of a property of the _Global Object_.
And as Asen already noted, the test is bogus. Better:

var _global = this;
var NATIVE_JSON_PARSE_SUPPORT =
typeof _global.JSON == "object" && _global.JSON
&& typeof JSON.parse == "function"
&& JSON.parse('{"x": "42"}').x == "42";

> | function evalResponse(responseText) {
> | if(NATIVE_JSON_PARSE_SUPPORT) {
> | try {
> | return JSON.parse(responseText);
> | } catch(ex) {
> | return "Error";
> | }

Don't. Let the code throw the original exception (`SyntaxError') or a user-
defined one (e.g., `JSONError') instead. (Unfortunately, the ES5 authors
neglected to define an easily distinguishable exception type for JSON
parsing.)


PointedEars
--
var bugRiddenCrashPronePieceOfJunk = (
navigator.userAgent.indexOf('MSIE 5') != -1
&& navigator.userAgent.indexOf('Mac') != -1
) // Plone, register_function.js:16
From: Garrett Smith on
On 6/8/2010 11:31 AM, Asen Bozhilov wrote:
> Garrett Smith wrote:
>
>> | In contrast, eval runs in the calling context's scope.
>
> As you know, that is not true in ECMA-262-5 strict mode. And `eval' is
> not run in calling execution context. ECMA-262-3 define `10.1.2 Types
> of Executable Code` and eval code is a part of that section. So eval
> code is running at separate execution context. That execution context
> use the same Variable Object and `this` value as calling execution
> context.
>

Yes that is what I meant to say. eval gets is scope from the calling
context.

>
>> | Where supported, JSON.parse may be used.
>> |
>> | var NATIVE_JSON_PARSE_SUPPORT = window.JSON&&
>> | typeof JSON.parse === 'function'&&
>> | JSON.parse('true').test;
>
> window.JSON ? Why do you use `window'? At environment where Global
> Object does not have property `window' or refer host object your code
> has two options:
>

Can use `this.JSON` in global context.

> - ReferenceError `window' is not defined
> - Result of expression is evaluated to `false'
>

True, for an environment with no window.


[...]

>
> What do you think about the follow string:
>
> '{JSON : false}'
>

Benign. Worse: any new or call expressions -- those would run to, for
Function().

> By JSON syntax grammar that is not syntactical valid. So if I run your
> code in implementation where is presented built-in `JSON' I will have
> result "Error". If I run the code in implementation without `JSON'
> that string will be evaluated as well because is bound by syntax rules
> of `11.1.5 Object Initialiser`. Think about it ;~)
>

If JSON is going to be used, then the fallback should work just the same.

The problem is that I don't have a good regexp filter to test valid JSON.

JSON.org does, but it seems flawed.

http://www.json.org/json2.js

The filter allows invalid JSON through. For example, passing in "{,,1]}"
results in a truthy value that is then passed to eval. The problem wit
that is the error that is thrown is different. Now, nstead of "invalid
JSON", you'll get something like "invalid property id.".

Example:
// Input
var text = "{,,1]}";

// Code from json2.js
/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(
/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]\d+)?/g,
']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))

Result:

In browsers with no JSON support, the result is going to be true, and if
`text` is subsequently passed to Function, the result is going to be a
different error message.

I did some searching for isValidJSON to see if anyone wrote one. I
learned that apparently jQuery has taken the same approach I proposed.
That is: Use JSON where available and where not, use a fallback enforces
valid JSON. Having a look:

| parseJSON: function( data ) {
| if ( typeof data !== "string" || !data ) {
| return null;
| }

What's that? An empty string becomes null? Why only allow strings?
ECMAScript Ed 5 requires that the argument be converted to a string.

Next I see:

| // Make sure the incoming data is actual JSON
| // Logic borrowed from http://json.org/json2.js

The RegExp in json2.js does not make sure the code is valid JSON.

jQuery's approach is no good, but the function's got a better name so
I'll use that. Back to the problem: I want to find a good regexp to
verify if something is JSON or not so that I can provide an equivalent
fallback.

// TODO: define validJSONExp.
var parseJSON = NATIVE_JSON_PARSE_SUPPORT ?
function(responseText) {
return JSON.parse(responseText);
} :
function(responseText) {
if(validJSONExp.test(responseText)) {
return new Function("return(" + responseText + ")")();
} else {
throw SyntaxError("JSON parse error");
}
};

Garrett
From: Garrett Smith on
On 6/8/2010 11:59 AM, Thomas 'PointedEars' Lahn wrote:
> Garrett Smith wrote:

[...]

> Don't. Let the code throw the original exception (`SyntaxError') or a user-
> defined one (e.g., `JSONError') instead. (Unfortunately, the ES5 authors
> neglected to define an easily distinguishable exception type for JSON
> parsing.)
>

JSON was designed to throw an error with the least amount of information
possible. This is, AIUI, to thwart attackers.

If a fallback is made, the fallback will also throw a similar error. In
that case, it should be fairly clear that the interface performs
equivalently across implementations.

Getting this right and getting the code short enough for the FAQ seems
to be a challenge. Meeting those goals, the result should be valuable
and appreciated by many.

I'm also working on something else and that thing is wearing me out.

Garrett