Home | History | Annotate | Download | only in mjsunit
      1 // Copyright 2009 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 // Date toJSON
     29 assertEquals("1970-01-01T00:00:00.000Z", new Date(0).toJSON());
     30 assertEquals("1979-01-11T08:00:00.000Z", new Date("1979-01-11 08:00 GMT").toJSON());
     31 assertEquals("2005-05-05T05:05:05.000Z", new Date("2005-05-05 05:05:05 GMT").toJSON());
     32 var n1 = new Date(10000);
     33 n1.toISOString = function () { return "foo"; };
     34 assertEquals("foo", n1.toJSON());
     35 var n2 = new Date(10001);
     36 n2.toISOString = null;
     37 assertThrows(function () { n2.toJSON(); }, TypeError);
     38 var n4 = new Date(10003);
     39 n4.toISOString = function () {
     40   assertEquals(0, arguments.length);
     41   assertEquals(this, n4);
     42   return null;
     43 };
     44 assertEquals(null, n4.toJSON());
     45 
     46 assertTrue(Object.prototype === JSON.__proto__);
     47 assertEquals("[object JSON]", Object.prototype.toString.call(JSON));
     48 
     49 //Test Date.prototype.toJSON as generic function.
     50 var d1 = {toJSON: Date.prototype.toJSON,
     51          toISOString: function() { return 42; }};
     52 assertEquals(42, d1.toJSON());
     53 
     54 var d2 = {toJSON: Date.prototype.toJSON,
     55           valueOf: function() { return Infinity; },
     56           toISOString: function() { return 42; }};
     57 assertEquals(null, d2.toJSON());
     58 
     59 var d3 = {toJSON: Date.prototype.toJSON,
     60           valueOf: "not callable",
     61           toString: function() { return Infinity; },
     62           toISOString: function() { return 42; }};
     63 
     64 assertEquals(null, d3.toJSON());
     65 
     66 var d4 = {toJSON: Date.prototype.toJSON,
     67           valueOf: "not callable",
     68           toString: "not callable either",
     69           toISOString: function() { return 42; }};
     70 assertThrows("d4.toJSON()", TypeError);  // ToPrimitive throws.
     71 
     72 var d5 = {toJSON: Date.prototype.toJSON,
     73           valueOf: "not callable",
     74           toString: function() { return "Infinity"; },
     75           toISOString: function() { return 42; }};
     76 assertEquals(42, d5.toJSON());
     77 
     78 var d6 = {toJSON: Date.prototype.toJSON,
     79           toISOString: function() { return ["not primitive"]; }};
     80 assertEquals(["not primitive"], d6.toJSON());
     81 
     82 var d7 = {toJSON: Date.prototype.toJSON,
     83           ISOString: "not callable"};
     84 assertThrows("d7.toJSON()", TypeError);
     85 
     86 // DontEnum
     87 for (var p in this) {
     88   assertFalse(p == "JSON");
     89 }
     90 
     91 // Parse
     92 assertEquals({}, JSON.parse("{}"));
     93 assertEquals({42:37}, JSON.parse('{"42":37}'));
     94 assertEquals(null, JSON.parse("null"));
     95 assertEquals(true, JSON.parse("true"));
     96 assertEquals(false, JSON.parse("false"));
     97 assertEquals("foo", JSON.parse('"foo"'));
     98 assertEquals("f\no", JSON.parse('"f\\no"'));
     99 assertEquals("\b\f\n\r\t\"\u2028\/\\",
    100              JSON.parse('"\\b\\f\\n\\r\\t\\"\\u2028\\/\\\\"'));
    101 assertEquals([1.1], JSON.parse("[1.1]"));
    102 assertEquals([1], JSON.parse("[1.0]"));
    103 
    104 assertEquals(0, JSON.parse("0"));
    105 assertEquals(1, JSON.parse("1"));
    106 assertEquals(0.1, JSON.parse("0.1"));
    107 assertEquals(1.1, JSON.parse("1.1"));
    108 assertEquals(1.1, JSON.parse("1.100000"));
    109 assertEquals(1.111111, JSON.parse("1.111111"));
    110 assertEquals(-0, JSON.parse("-0"));
    111 assertEquals(-1, JSON.parse("-1"));
    112 assertEquals(-0.1, JSON.parse("-0.1"));
    113 assertEquals(-1.1, JSON.parse("-1.1"));
    114 assertEquals(-1.1, JSON.parse("-1.100000"));
    115 assertEquals(-1.111111, JSON.parse("-1.111111"));
    116 assertEquals(11, JSON.parse("1.1e1"));
    117 assertEquals(11, JSON.parse("1.1e+1"));
    118 assertEquals(0.11, JSON.parse("1.1e-1"));
    119 assertEquals(11, JSON.parse("1.1E1"));
    120 assertEquals(11, JSON.parse("1.1E+1"));
    121 assertEquals(0.11, JSON.parse("1.1E-1"));
    122 
    123 assertEquals([], JSON.parse("[]"));
    124 assertEquals([1], JSON.parse("[1]"));
    125 assertEquals([1, "2", true, null], JSON.parse('[1, "2", true, null]'));
    126 
    127 assertEquals("", JSON.parse('""'));
    128 assertEquals(["", "", -0, ""], JSON.parse('[    ""  ,    ""  ,   -0,    ""]'));
    129 assertEquals("", JSON.parse('""'));
    130 
    131 
    132 function GetFilter(name) {
    133   function Filter(key, value) {
    134     return (key == name) ? undefined : value;
    135   }
    136   return Filter;
    137 }
    138 
    139 var pointJson = '{"x": 1, "y": 2}';
    140 assertEquals({'x': 1, 'y': 2}, JSON.parse(pointJson));
    141 assertEquals({'x': 1}, JSON.parse(pointJson, GetFilter('y')));
    142 assertEquals({'y': 2}, JSON.parse(pointJson, GetFilter('x')));
    143 assertEquals([1, 2, 3], JSON.parse("[1, 2, 3]"));
    144 assertEquals([1, undefined, 3], JSON.parse("[1, 2, 3]", GetFilter(1)));
    145 assertEquals([1, 2, undefined], JSON.parse("[1, 2, 3]", GetFilter(2)));
    146 
    147 function DoubleNumbers(key, value) {
    148   return (typeof value == 'number') ? 2 * value : value;
    149 }
    150 
    151 var deepObject = '{"a": {"b": 1, "c": 2}, "d": {"e": {"f": 3}}}';
    152 assertEquals({"a": {"b": 1, "c": 2}, "d": {"e": {"f": 3}}},
    153              JSON.parse(deepObject));
    154 assertEquals({"a": {"b": 2, "c": 4}, "d": {"e": {"f": 6}}},
    155              JSON.parse(deepObject, DoubleNumbers));
    156 
    157 function TestInvalid(str) {
    158   assertThrows(function () { JSON.parse(str); }, SyntaxError);
    159 }
    160 
    161 TestInvalid('abcdef');
    162 TestInvalid('isNaN()');
    163 TestInvalid('{"x": [1, 2, deepObject]}');
    164 TestInvalid('[1, [2, [deepObject], 3], 4]');
    165 TestInvalid('function () { return 0; }');
    166 
    167 TestInvalid("[1, 2");
    168 TestInvalid('{"x": 3');
    169 
    170 // JavaScript number literals not valid in JSON.
    171 TestInvalid('[01]');
    172 TestInvalid('[.1]');
    173 TestInvalid('[1.]');
    174 TestInvalid('[1.e1]');
    175 TestInvalid('[-.1]');
    176 TestInvalid('[-1.]');
    177 
    178 // Plain invalid number literals.
    179 TestInvalid('-');
    180 TestInvalid('--1');
    181 TestInvalid('-1e');
    182 TestInvalid('1e--1]');
    183 TestInvalid('1e+-1');
    184 TestInvalid('1e-+1');
    185 TestInvalid('1e++1');
    186 
    187 // JavaScript string literals not valid in JSON.
    188 TestInvalid("'single quote'");  // Valid JavaScript
    189 TestInvalid('"\\a invalid escape"');
    190 TestInvalid('"\\v invalid escape"');  // Valid JavaScript
    191 TestInvalid('"\\\' invalid escape"');  // Valid JavaScript
    192 TestInvalid('"\\x42 invalid escape"');  // Valid JavaScript
    193 TestInvalid('"\\u202 invalid escape"');
    194 TestInvalid('"\\012 invalid escape"');
    195 TestInvalid('"Unterminated string');
    196 TestInvalid('"Unterminated string\\"');
    197 TestInvalid('"Unterminated string\\\\\\"');
    198 
    199 // Test bad JSON that would be good JavaScript (ES5).
    200 TestInvalid("{true:42}");
    201 TestInvalid("{false:42}");
    202 TestInvalid("{null:42}");
    203 TestInvalid("{'foo':42}");
    204 TestInvalid("{42:42}");
    205 TestInvalid("{0:42}");
    206 TestInvalid("{-1:42}");
    207 
    208 // Test for trailing garbage detection.
    209 TestInvalid('42 px');
    210 TestInvalid('42 .2');
    211 TestInvalid('42 2');
    212 TestInvalid('42 e1');
    213 TestInvalid('"42" ""');
    214 TestInvalid('"42" ""');
    215 TestInvalid('"" ""');
    216 TestInvalid('true ""');
    217 TestInvalid('false ""');
    218 TestInvalid('null ""');
    219 TestInvalid('null ""');
    220 TestInvalid('[] ""');
    221 TestInvalid('[true] ""');
    222 TestInvalid('{} ""');
    223 TestInvalid('{"x":true} ""');
    224 TestInvalid('"Garbage""After string"');
    225 
    226 // Stringify
    227 
    228 assertEquals("true", JSON.stringify(true));
    229 assertEquals("false", JSON.stringify(false));
    230 assertEquals("null", JSON.stringify(null));
    231 assertEquals("false", JSON.stringify({toJSON: function () { return false; }}));
    232 assertEquals("4", JSON.stringify(4));
    233 assertEquals('"foo"', JSON.stringify("foo"));
    234 assertEquals("null", JSON.stringify(Infinity));
    235 assertEquals("null", JSON.stringify(-Infinity));
    236 assertEquals("null", JSON.stringify(NaN));
    237 assertEquals("4", JSON.stringify(new Number(4)));
    238 assertEquals('"bar"', JSON.stringify(new String("bar")));
    239 
    240 assertEquals('"foo\\u0000bar"', JSON.stringify("foo\0bar"));
    241 assertEquals('"f\\"o\'o\\\\b\\ba\\fr\\nb\\ra\\tz"',
    242              JSON.stringify("f\"o\'o\\b\ba\fr\nb\ra\tz"));
    243 
    244 assertEquals("[1,2,3]", JSON.stringify([1, 2, 3]));
    245 assertEquals("[\n 1,\n 2,\n 3\n]", JSON.stringify([1, 2, 3], null, 1));
    246 assertEquals("[\n  1,\n  2,\n  3\n]", JSON.stringify([1, 2, 3], null, 2));
    247 assertEquals("[\n  1,\n  2,\n  3\n]",
    248              JSON.stringify([1, 2, 3], null, new Number(2)));
    249 assertEquals("[\n^1,\n^2,\n^3\n]", JSON.stringify([1, 2, 3], null, "^"));
    250 assertEquals("[\n^1,\n^2,\n^3\n]",
    251              JSON.stringify([1, 2, 3], null, new String("^")));
    252 assertEquals("[\n 1,\n 2,\n [\n  3,\n  [\n   4\n  ],\n  5\n ],\n 6,\n 7\n]",
    253              JSON.stringify([1, 2, [3, [4], 5], 6, 7], null, 1));
    254 assertEquals("[]", JSON.stringify([], null, 1));
    255 assertEquals("[1,2,[3,[4],5],6,7]",
    256              JSON.stringify([1, 2, [3, [4], 5], 6, 7], null));
    257 assertEquals("[2,4,[6,[8],10],12,14]",
    258              JSON.stringify([1, 2, [3, [4], 5], 6, 7], DoubleNumbers));
    259 assertEquals('["a","ab","abc"]', JSON.stringify(["a","ab","abc"]));
    260 
    261 var circular = [1, 2, 3];
    262 circular[2] = circular;
    263 assertThrows(function () { JSON.stringify(circular); }, TypeError);
    264 
    265 var singleton = [];
    266 var multiOccurrence = [singleton, singleton, singleton];
    267 assertEquals("[[],[],[]]", JSON.stringify(multiOccurrence));
    268 
    269 assertEquals('{"x":5,"y":6}', JSON.stringify({x:5,y:6}));
    270 assertEquals('{"x":5}', JSON.stringify({x:5,y:6}, ['x']));
    271 assertEquals('{\n "a": "b",\n "c": "d"\n}',
    272              JSON.stringify({a:"b",c:"d"}, null, 1));
    273 assertEquals('{"y":6,"x":5}', JSON.stringify({x:5,y:6}, ['y', 'x']));
    274 
    275 // toJSON get string keys.
    276 var checker = {};
    277 var array = [checker];
    278 checker.toJSON = function(key) { return 1 + key; };
    279 assertEquals('["10"]', JSON.stringify(array));
    280 
    281 // The gap is capped at ten characters if specified as string.
    282 assertEquals('{\n          "a": "b",\n          "c": "d"\n}',
    283               JSON.stringify({a:"b",c:"d"}, null,
    284                              "          /*characters after 10th*/"));
    285 
    286 //The gap is capped at ten characters if specified as number.
    287 assertEquals('{\n          "a": "b",\n          "c": "d"\n}',
    288               JSON.stringify({a:"b",c:"d"}, null, 15));
    289 
    290 // Replaced wrapped primitives are unwrapped.
    291 function newx(k, v)  { return (k == "x") ? new v(42) : v; }
    292 assertEquals('{"x":"42"}', JSON.stringify({x: String}, newx));
    293 assertEquals('{"x":42}', JSON.stringify({x: Number}, newx));
    294 assertEquals('{"x":true}', JSON.stringify({x: Boolean}, newx));
    295 
    296 assertEquals(undefined, JSON.stringify(undefined));
    297 assertEquals(undefined, JSON.stringify(function () { }));
    298 // Arrays with missing, undefined or function elements have those elements
    299 // replaced by null.
    300 assertEquals("[null,null,null]",
    301              JSON.stringify([undefined,,function(){}]));
    302 
    303 // Objects with undefined or function properties (including replaced properties)
    304 // have those properties ignored.
    305 assertEquals('{}',
    306              JSON.stringify({a: undefined, b: function(){}, c: 42, d: 42},
    307                             function(k, v) { if (k == "c") return undefined;
    308                                              if (k == "d") return function(){};
    309                                              return v; }));
    310 
    311 TestInvalid('1); throw "foo"; (1');
    312 
    313 var x = 0;
    314 eval("(1); x++; (1)");
    315 TestInvalid('1); x++; (1');
    316 
    317 // Test string conversion of argument.
    318 var o = { toString: function() { return "42"; } };
    319 assertEquals(42, JSON.parse(o));
    320 
    321 
    322 for (var i = 0; i < 65536; i++) {
    323   var string = String.fromCharCode(i);
    324   var encoded = JSON.stringify(string);
    325   var expected = "uninitialized";
    326   // Following the ES5 specification of the abstraction function Quote.
    327   if (string == '"' || string == '\\') {
    328     // Step 2.a
    329     expected = '\\' + string;
    330   } else if ("\b\t\n\r\f".indexOf(string) >= 0) {
    331     // Step 2.b
    332     if (string == '\b') expected = '\\b';
    333     else if (string == '\t') expected = '\\t';
    334     else if (string == '\n') expected = '\\n';
    335     else if (string == '\f') expected = '\\f';
    336     else if (string == '\r') expected = '\\r';
    337   } else if (i < 32) {
    338     // Step 2.c
    339     if (i < 16) {
    340       expected = "\\u000" + i.toString(16);
    341     } else {
    342       expected = "\\u00" + i.toString(16);
    343     }
    344   } else {
    345     expected = string;
    346   }
    347   assertEquals('"' + expected + '"', encoded, "Codepoint " + i);
    348 }
    349 
    350 
    351 // Ensure that wrappers and callables are handled correctly.
    352 var num37 = new Number(42);
    353 num37.valueOf = function() { return 37; };
    354 
    355 var numFoo = new Number(42);
    356 numFoo.valueOf = "not callable";
    357 numFoo.toString = function() { return "foo"; };
    358 
    359 var numTrue = new Number(42);
    360 numTrue.valueOf = function() { return true; }
    361 
    362 var strFoo = new String("bar");
    363 strFoo.toString = function() { return "foo"; };
    364 
    365 var str37 = new String("bar");
    366 str37.toString = "not callable";
    367 str37.valueOf = function() { return 37; };
    368 
    369 var strTrue = new String("bar");
    370 strTrue.toString = function() { return true; }
    371 
    372 var func = function() { /* Is callable */ };
    373 
    374 var funcJSON = function() { /* Is callable */ };
    375 funcJSON.toJSON = function() { return "has toJSON"; };
    376 
    377 var re = /Is callable/;
    378 
    379 var reJSON = /Is callable/;
    380 reJSON.toJSON = function() { return "has toJSON"; };
    381 
    382 assertEquals(
    383     '[37,null,1,"foo","37","true",null,"has toJSON",{},"has toJSON"]',
    384     JSON.stringify([num37, numFoo, numTrue,
    385                     strFoo, str37, strTrue,
    386                     func, funcJSON, re, reJSON]));
    387 
    388 
    389 var oddball = Object(42);
    390 oddball.__proto__ = { __proto__: null, toString: function() { return true; } };
    391 assertEquals('1', JSON.stringify(oddball));
    392 
    393 var getCount = 0;
    394 var callCount = 0;
    395 var counter = { get toJSON() { getCount++;
    396                                return function() { callCount++;
    397                                                    return 42; }; } };
    398 
    399 // RegExps are not callable, so they are stringified as objects.
    400 assertEquals('{}', JSON.stringify(/regexp/));
    401 assertEquals('42', JSON.stringify(counter));
    402 assertEquals(1, getCount);
    403 assertEquals(1, callCount);
    404 
    405 var oddball2 = Object(42);
    406 var oddball3 = Object("foo");
    407 oddball3.__proto__ = { __proto__: null,
    408                        toString: "not callable",
    409                        valueOf: function() { return true; } };
    410 oddball2.__proto__ = { __proto__: null,
    411                        toJSON: function () { return oddball3; } }
    412 assertEquals('"true"', JSON.stringify(oddball2));
    413 
    414 
    415 var falseNum = Object("37");
    416 falseNum.__proto__ = Number.prototype;
    417 falseNum.toString = function() { return 42; };
    418 assertEquals('"42"', JSON.stringify(falseNum));
    419 
    420 // We don't currently allow plain properties called __proto__ in JSON
    421 // objects in JSON.parse. Instead we read them as we would JS object
    422 // literals. If we change that, this test should change with it.
    423 //
    424 // Parse a non-object value as __proto__. This must not create a
    425 // __proto__ property different from the original, and should not
    426 // change the original.
    427 var o = JSON.parse('{"__proto__":5}');
    428 assertEquals(Object.prototype, o.__proto__);  // __proto__ isn't changed.
    429 assertEquals(0, Object.keys(o).length);  // __proto__ isn't added as enumerable.
    430 
    431 
    432 
    433