"../core"
], function( jQuery ) {
-var rvalidchars = /^[\],:{}\s]*$/,
- rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
- rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
- rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g;
+var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
jQuery.parseJSON = function( data ) {
// Attempt to parse using the native JSON parser first
if ( window.JSON && window.JSON.parse ) {
- return window.JSON.parse( data );
+ // Support: Android 2.3
+ // Workaround failure to string-cast null input
+ return window.JSON.parse( data + "" );
}
- if ( data === null ) {
- return data;
- }
-
- if ( typeof data === "string" ) {
+ var requireNonComma,
+ depth = null,
+ str = jQuery.trim( data + "" );
- // Make sure leading/trailing whitespace is removed (IE can't handle it)
- data = jQuery.trim( data );
+ // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
+ // after removing valid tokens
+ return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {
- if ( data ) {
- // Make sure the incoming data is actual JSON
- // Logic borrowed from http://json.org/json2.js
- if ( rvalidchars.test( data.replace( rvalidescape, "@" )
- .replace( rvalidtokens, "]" )
- .replace( rvalidbraces, "")) ) {
+ // Force termination if we see a misplaced comma
+ if ( requireNonComma && comma ) {
+ depth = 0;
+ }
- return ( new Function( "return " + data ) )();
- }
+ // Perform no more replacements after returning to outermost depth
+ if ( depth === 0 ) {
+ return token;
}
- }
- jQuery.error( "Invalid JSON: " + data );
+ // Commas must not follow "[", "{", or ","
+ requireNonComma = open || comma;
+
+ // Determine new depth
+ // array/object open ("[" or "{"): depth += true - false (increment)
+ // array/object close ("]" or "}"): depth += false - true (decrement)
+ // other cases ("," or primitive): depth += true - true (numeric cast)
+ depth += !close - !open;
+
+ // Remove this token
+ return "";
+ }) ) ?
+ ( Function( "return " + str ) )() :
+ jQuery.error( "Invalid JSON: " + data );
};
return jQuery.parseJSON;
equal( jQuery.parseHTML("<td><td>")[ 1 ].parentNode.nodeType, 11, "parentNode should be documentFragment" );
});
-test("jQuery.parseJSON", function(){
- expect( 9 );
+test("jQuery.parseJSON", function() {
+ expect( 20 );
+
+ strictEqual( jQuery.parseJSON( null ), null, "primitive null" );
+ strictEqual( jQuery.parseJSON("0.88"), 0.88, "Number" );
+ strictEqual(
+ jQuery.parseJSON("\" \\\" \\\\ \\/ \\b \\f \\n \\r \\t \\u007E \\u263a \""),
+ " \" \\ / \b \f \n \r \t ~ \u263A ",
+ "String escapes"
+ );
+ deepEqual( jQuery.parseJSON("{}"), {}, "Empty object" );
+ deepEqual( jQuery.parseJSON("{\"test\":1}"), { "test": 1 }, "Plain object" );
+ deepEqual( jQuery.parseJSON("[0]"), [ 0 ], "Simple array" );
+
+ deepEqual(
+ jQuery.parseJSON("[ \"string\", -4.2, 2.7180e0, 3.14E-1, {}, [], true, false, null ]"),
+ [ "string", -4.2, 2.718, 0.314, {}, [], true, false, null ],
+ "Array of all data types"
+ );
+ deepEqual(
+ jQuery.parseJSON( "{ \"string\": \"\", \"number\": 4.2e+1, \"object\": {}," +
+ "\"array\": [[]], \"boolean\": [ true, false ], \"null\": null }"),
+ { string: "", number: 42, object: {}, array: [[]], boolean: [ true, false ], "null": null },
+ "Dictionary of all data types"
+ );
+
+ deepEqual( jQuery.parseJSON("\n{\"test\":1}\t"), { "test": 1 },
+ "Leading and trailing whitespace are ignored" );
- equal( jQuery.parseJSON( null ), null, "Actual null returns null" );
- equal( jQuery.isEmptyObject( jQuery.parseJSON("{}") ), true, "Empty object returns empty object" );
- deepEqual( jQuery.parseJSON("{\"test\":1}"), { "test": 1 }, "Plain object parses" );
- deepEqual( jQuery.parseJSON("\n{\"test\":1}"), { "test": 1 }, "Leading whitespaces are ignored." );
raises(function() {
jQuery.parseJSON();
}, null, "Undefined raises an error" );
raises(function() {
jQuery.parseJSON("''");
}, null, "Single-quoted string raises an error" );
+ /*
+
+ // Broken on IE8
+ raises(function() {
+ jQuery.parseJSON("\" \\a \"");
+ }, null, "Invalid string escape raises an error" );
+
+ // Broken on IE8, Safari 5.1 Windows
+ raises(function() {
+ jQuery.parseJSON("\"\t\"");
+ }, null, "Unescaped control character raises an error" );
+
+ // Broken on IE8
+ raises(function() {
+ jQuery.parseJSON(".123");
+ }, null, "Number with no integer component raises an error" );
+
+ */
+ raises(function() {
+ var result = jQuery.parseJSON("0101");
+
+ // Support: IE9+
+ // Ensure base-10 interpretation on browsers that erroneously accept leading-zero numbers
+ if ( result === 101 ) {
+ throw new Error("close enough");
+ }
+ }, null, "Leading-zero number raises an error or is parsed as decimal" );
raises(function() {
jQuery.parseJSON("{a:1}");
}, null, "Unquoted property raises an error" );
raises(function() {
jQuery.parseJSON("{'a':1}");
}, null, "Single-quoted property raises an error" );
+ raises(function() {
+ jQuery.parseJSON("[,]");
+ }, null, "Array element elision raises an error" );
+ raises(function() {
+ jQuery.parseJSON("{},[]");
+ }, null, "Comma expression raises an error" );
+ raises(function() {
+ jQuery.parseJSON("[]\n,{}");
+ }, null, "Newline-containing comma expression raises an error" );
+ raises(function() {
+ jQuery.parseJSON("\"\"\n\"\"");
+ }, null, "Automatic semicolon insertion raises an error" );
+
+ strictEqual( jQuery.parseJSON([ 0 ]), 0, "Input cast to string" );
});
test("jQuery.parseXML", 8, function(){