| // Copyright 2013 The Closure Library Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS-IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| goog.provide('goog.jsonTest'); |
| goog.setTestOnly('goog.jsonTest'); |
| |
| goog.require('goog.functions'); |
| goog.require('goog.json'); |
| goog.require('goog.testing.jsunit'); |
| goog.require('goog.userAgent'); |
| |
| function allChars(start, end, opt_allowControlCharacters) { |
| var sb = []; |
| for (var i = start; i < end; i++) { |
| // unicode without the control characters 0x00 - 0x1f |
| if (opt_allowControlCharacters || i > 0x1f) { |
| sb.push(String.fromCharCode(i)); |
| } |
| } |
| return sb.join(''); |
| } |
| |
| // serialization |
| |
| function testStringSerialize() { |
| assertSerialize('""', ''); |
| |
| // unicode |
| var str = allChars(0, 10000); |
| eval(goog.json.serialize(str)); |
| |
| assertSerialize('"true"', 'true'); |
| assertSerialize('"false"', 'false'); |
| assertSerialize('"null"', 'null'); |
| assertSerialize('"0"', '0'); |
| } |
| |
| function testNullSerialize() { |
| assertSerialize('null', null); |
| assertSerialize('null', undefined); |
| assertSerialize('null', NaN); |
| |
| assertSerialize('0', 0); |
| assertSerialize('""', ''); |
| assertSerialize('false', false); |
| } |
| |
| function testNullPropertySerialize() { |
| assertSerialize('{"a":null}', {'a': null}); |
| assertSerialize('{"a":null}', {'a': undefined}); |
| } |
| |
| function testNumberSerialize() { |
| assertSerialize('0', 0); |
| assertSerialize('12345', 12345); |
| assertSerialize('-12345', -12345); |
| |
| assertSerialize('0.1', 0.1); |
| // the leading zero may not be omitted |
| assertSerialize('0.1', .1); |
| |
| // no leading + |
| assertSerialize('1', +1); |
| |
| // either format is OK |
| var s = goog.json.serialize(1e50); |
| assertTrue('1e50', |
| s == '1e50' || s == '1E50' || |
| s == '1e+50' || s == '1E+50'); |
| |
| // either format is OK |
| s = goog.json.serialize(1e-50); |
| assertTrue('1e50', s == '1e-50' || s == '1E-50'); |
| |
| // These numbers cannot be represented in JSON |
| assertSerialize('null', NaN); |
| assertSerialize('null', Infinity); |
| assertSerialize('null', -Infinity); |
| } |
| |
| function testBooleanSerialize() { |
| assertSerialize('true', true); |
| assertSerialize('"true"', 'true'); |
| |
| assertSerialize('false', false); |
| assertSerialize('"false"', 'false'); |
| } |
| |
| function testArraySerialize() { |
| assertSerialize('[]', []); |
| assertSerialize('[1]', [1]); |
| assertSerialize('[1,2]', [1, 2]); |
| assertSerialize('[1,2,3]', [1, 2, 3]); |
| assertSerialize('[[]]', [[]]); |
| |
| assertNotEquals('{length:0}', goog.json.serialize({length: 0}), '[]'); |
| } |
| |
| function testObjectSerialize_emptyObject() { |
| assertSerialize('{}', {}); |
| } |
| |
| function testObjectSerialize_oneItem() { |
| assertSerialize('{"a":"b"}', {a: 'b'}); |
| } |
| |
| function testObjectSerialize_twoItems() { |
| assertEquals('{"a":"b","c":"d"}', |
| goog.json.serialize({a: 'b', c: 'd'}), |
| '{"a":"b","c":"d"}'); |
| } |
| |
| function testObjectSerialize_whitespace() { |
| assertSerialize('{" ":" "}', {' ': ' '}); |
| } |
| |
| function testSerializeSkipFunction() { |
| var object = { |
| s: 'string value', |
| b: true, |
| i: 100, |
| f: function() { var x = 'x'; } |
| }; |
| assertSerialize('', object.f); |
| assertSerialize('{"s":"string value","b":true,"i":100}', object); |
| } |
| |
| function testObjectSerialize_array() { |
| assertNotEquals('[0,1]', goog.json.serialize([0, 1]), '{"0":"0","1":"1"}'); |
| } |
| |
| function testObjectSerialize_recursion() { |
| if (goog.userAgent.WEBKIT) { |
| return; // this makes safari 4 crash. |
| } |
| |
| var anObject = {}; |
| anObject.thisObject = anObject; |
| assertThrows('expected recursion exception', function() { |
| goog.json.serialize(anObject); |
| }); |
| } |
| |
| function testObjectSerializeWithHasOwnProperty() { |
| var object = {'hasOwnProperty': null}; |
| if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('9')) { |
| assertEquals('{}', goog.json.serialize(object)); |
| } else { |
| assertEquals('{"hasOwnProperty":null}', goog.json.serialize(object)); |
| } |
| } |
| |
| // parsing |
| |
| function testStringParse() { |
| |
| assertEquals('Empty string', goog.json.parse('""'), ''); |
| assertEquals('whitespace string', goog.json.parse('" "'), ' '); |
| |
| // unicode without the control characters 0x00 - 0x1f, 0x7f - 0x9f |
| var str = allChars(0, 1000); |
| var jsonString = goog.json.serialize(str); |
| var a = eval(jsonString); |
| assertEquals('unicode string', goog.json.parse(jsonString), a); |
| |
| assertEquals('true as a string', goog.json.parse('"true"'), 'true'); |
| assertEquals('false as a string', goog.json.parse('"false"'), 'false'); |
| assertEquals('null as a string', goog.json.parse('"null"'), 'null'); |
| assertEquals('number as a string', goog.json.parse('"0"'), '0'); |
| } |
| |
| function testStringUnsafeParse() { |
| |
| assertEquals('Empty string', goog.json.unsafeParse('""'), ''); |
| assertEquals('whitespace string', goog.json.unsafeParse('" "'), ' '); |
| |
| // unicode |
| var str = allChars(0, 1000); |
| var jsonString = goog.json.serialize(str); |
| var a = eval(jsonString); |
| assertEquals('unicode string', goog.json.unsafeParse(jsonString), a); |
| |
| assertEquals('true as a string', goog.json.unsafeParse('"true"'), 'true'); |
| assertEquals('false as a string', goog.json.unsafeParse('"false"'), 'false'); |
| assertEquals('null as a string', goog.json.unsafeParse('"null"'), 'null'); |
| assertEquals('number as a string', goog.json.unsafeParse('"0"'), '0'); |
| } |
| |
| function testNullParse() { |
| assertEquals('null', goog.json.parse(null), null); |
| assertEquals('null', goog.json.parse('null'), null); |
| |
| assertNotEquals('0', goog.json.parse('0'), null); |
| assertNotEquals('""', goog.json.parse('""'), null); |
| assertNotEquals('false', goog.json.parse('false'), null); |
| } |
| |
| function testNullUnsafeParse() { |
| assertEquals('null', goog.json.unsafeParse(null), null); |
| assertEquals('null', goog.json.unsafeParse('null'), null); |
| |
| assertNotEquals('0', goog.json.unsafeParse('0'), null); |
| assertNotEquals('""', goog.json.unsafeParse('""'), null); |
| assertNotEquals('false', goog.json.unsafeParse('false'), null); |
| } |
| |
| function testNumberParse() { |
| assertEquals('0', goog.json.parse('0'), 0); |
| assertEquals('12345', goog.json.parse('12345'), 12345); |
| assertEquals('-12345', goog.json.parse('-12345'), -12345); |
| |
| assertEquals('0.1', goog.json.parse('0.1'), 0.1); |
| |
| // either format is OK |
| assertEquals(1e50, goog.json.parse('1e50')); |
| assertEquals(1e50, goog.json.parse('1E50')); |
| assertEquals(1e50, goog.json.parse('1e+50')); |
| assertEquals(1e50, goog.json.parse('1E+50')); |
| |
| // either format is OK |
| assertEquals(1e-50, goog.json.parse('1e-50')); |
| assertEquals(1e-50, goog.json.parse('1E-50')); |
| } |
| |
| function testNumberUnsafeParse() { |
| assertEquals('0', goog.json.unsafeParse('0'), 0); |
| assertEquals('12345', goog.json.unsafeParse('12345'), 12345); |
| assertEquals('-12345', goog.json.unsafeParse('-12345'), -12345); |
| |
| assertEquals('0.1', goog.json.unsafeParse('0.1'), 0.1); |
| |
| // either format is OK |
| assertEquals(1e50, goog.json.unsafeParse('1e50')); |
| assertEquals(1e50, goog.json.unsafeParse('1E50')); |
| assertEquals(1e50, goog.json.unsafeParse('1e+50')); |
| assertEquals(1e50, goog.json.unsafeParse('1E+50')); |
| |
| // either format is OK |
| assertEquals(1e-50, goog.json.unsafeParse('1e-50')); |
| assertEquals(1e-50, goog.json.unsafeParse('1E-50')); |
| } |
| |
| function testBooleanParse() { |
| assertEquals('true', goog.json.parse('true'), true); |
| assertEquals('false', goog.json.parse('false'), false); |
| |
| assertNotEquals('0', goog.json.parse('0'), false); |
| assertNotEquals('"false"', goog.json.parse('"false"'), false); |
| assertNotEquals('null', goog.json.parse('null'), false); |
| |
| assertNotEquals('1', goog.json.parse('1'), true); |
| assertNotEquals('"true"', goog.json.parse('"true"'), true); |
| assertNotEquals('{}', goog.json.parse('{}'), true); |
| assertNotEquals('[]', goog.json.parse('[]'), true); |
| } |
| |
| function testBooleanUnsafeParse() { |
| assertEquals('true', goog.json.unsafeParse('true'), true); |
| assertEquals('false', goog.json.unsafeParse('false'), false); |
| |
| assertNotEquals('0', goog.json.unsafeParse('0'), false); |
| assertNotEquals('"false"', goog.json.unsafeParse('"false"'), false); |
| assertNotEquals('null', goog.json.unsafeParse('null'), false); |
| |
| assertNotEquals('1', goog.json.unsafeParse('1'), true); |
| assertNotEquals('"true"', goog.json.unsafeParse('"true"'), true); |
| assertNotEquals('{}', goog.json.unsafeParse('{}'), true); |
| assertNotEquals('[]', goog.json.unsafeParse('[]'), true); |
| } |
| |
| function testArrayParse() { |
| assertArrayEquals([], goog.json.parse('[]')); |
| assertArrayEquals([1], goog.json.parse('[1]')); |
| assertArrayEquals([1, 2], goog.json.parse('[1,2]')); |
| assertArrayEquals([1, 2, 3], goog.json.parse('[1,2,3]')); |
| assertArrayEquals([[]], goog.json.parse('[[]]')); |
| |
| // Note that array-holes are not valid json. However, goog.json.parse |
| // supports them so that clients can reap the security benefits of |
| // goog.json.parse even if they are using this non-standard format. |
| assertArrayEquals([1, /* hole */, 3], goog.json.parse('[1,,3]')); |
| |
| // make sure we do not get an array for something that looks like an array |
| assertFalse('{length:0}', 'push' in goog.json.parse('{"length":0}')); |
| } |
| |
| function testArrayUnsafeParse() { |
| function arrayEquals(a1, a2) { |
| if (a1.length != a2.length) { |
| return false; |
| } |
| for (var i = 0; i < a1.length; i++) { |
| if (a1[i] != a2[i]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| assertTrue('[]', arrayEquals(goog.json.unsafeParse('[]'), [])); |
| assertTrue('[1]', arrayEquals(goog.json.unsafeParse('[1]'), [1])); |
| assertTrue('[1,2]', arrayEquals(goog.json.unsafeParse('[1,2]'), [1, 2])); |
| assertTrue('[1,2,3]', |
| arrayEquals(goog.json.unsafeParse('[1,2,3]'), [1, 2, 3])); |
| assertTrue('[[]]', arrayEquals(goog.json.unsafeParse('[[]]')[0], [])); |
| |
| // make sure we do not get an array for something that looks like an array |
| assertFalse('{length:0}', 'push' in goog.json.unsafeParse('{"length":0}')); |
| } |
| |
| function testObjectParse() { |
| function objectEquals(a1, a2) { |
| for (var key in a1) { |
| if (a1[key] != a2[key]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| assertTrue('{}', objectEquals(goog.json.parse('{}'), {})); |
| assertTrue('{"a":"b"}', |
| objectEquals(goog.json.parse('{"a":"b"}'), {a: 'b'})); |
| assertTrue('{"a":"b","c":"d"}', |
| objectEquals(goog.json.parse('{"a":"b","c":"d"}'), |
| {a: 'b', c: 'd'})); |
| assertTrue('{" ":" "}', |
| objectEquals(goog.json.parse('{" ":" "}'), {' ': ' '})); |
| |
| // make sure we do not get an Object when it is really an array |
| assertTrue('[0,1]', 'length' in goog.json.parse('[0,1]')); |
| } |
| |
| function testObjectUnsafeParse() { |
| function objectEquals(a1, a2) { |
| for (var key in a1) { |
| if (a1[key] != a2[key]) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| assertTrue('{}', objectEquals(goog.json.unsafeParse('{}'), {})); |
| assertTrue('{"a":"b"}', |
| objectEquals(goog.json.unsafeParse('{"a":"b"}'), {a: 'b'})); |
| assertTrue('{"a":"b","c":"d"}', |
| objectEquals(goog.json.unsafeParse('{"a":"b","c":"d"}'), |
| {a: 'b', c: 'd'})); |
| assertTrue('{" ":" "}', |
| objectEquals(goog.json.unsafeParse('{" ":" "}'), {' ': ' '})); |
| |
| // make sure we do not get an Object when it is really an array |
| assertTrue('[0,1]', 'length' in goog.json.unsafeParse('[0,1]')); |
| } |
| |
| |
| function testForValidJson() { |
| function error_(msg, s) { |
| assertThrows(msg + ', Should have raised an exception: ' + s, |
| goog.partial(goog.json.parse, s)); |
| } |
| |
| error_('Non closed string', '"dasdas'); |
| error_('undefined is not valid json', 'undefined'); |
| |
| // These numbers cannot be represented in JSON |
| error_('NaN cannot be presented in JSON', 'NaN'); |
| error_('Infinity cannot be presented in JSON', 'Infinity'); |
| error_('-Infinity cannot be presented in JSON', '-Infinity'); |
| } |
| |
| function testIsNotValid() { |
| assertFalse(goog.json.isValid('t')); |
| assertFalse(goog.json.isValid('r')); |
| assertFalse(goog.json.isValid('u')); |
| assertFalse(goog.json.isValid('e')); |
| assertFalse(goog.json.isValid('f')); |
| assertFalse(goog.json.isValid('a')); |
| assertFalse(goog.json.isValid('l')); |
| assertFalse(goog.json.isValid('s')); |
| assertFalse(goog.json.isValid('n')); |
| assertFalse(goog.json.isValid('E')); |
| |
| assertFalse(goog.json.isValid('+')); |
| assertFalse(goog.json.isValid('-')); |
| |
| assertFalse(goog.json.isValid('t++')); |
| assertFalse(goog.json.isValid('++t')); |
| assertFalse(goog.json.isValid('t--')); |
| assertFalse(goog.json.isValid('--t')); |
| assertFalse(goog.json.isValid('-t')); |
| assertFalse(goog.json.isValid('+t')); |
| |
| assertFalse(goog.json.isValid('"\\"')); // "\" |
| assertFalse(goog.json.isValid('"\\')); // "\ |
| |
| // multiline string using \ at the end is not valid |
| assertFalse(goog.json.isValid('"a\\\nb"')); |
| |
| |
| assertFalse(goog.json.isValid('"\n"')); |
| assertFalse(goog.json.isValid('"\r"')); |
| assertFalse(goog.json.isValid('"\r\n"')); |
| // Disallow the unicode newlines |
| assertFalse(goog.json.isValid('"\u2028"')); |
| assertFalse(goog.json.isValid('"\u2029"')); |
| |
| assertFalse(goog.json.isValid(' ')); |
| assertFalse(goog.json.isValid('\n')); |
| assertFalse(goog.json.isValid('\r')); |
| assertFalse(goog.json.isValid('\r\n')); |
| |
| assertFalse(goog.json.isValid('t.r')); |
| |
| assertFalse(goog.json.isValid('1e')); |
| assertFalse(goog.json.isValid('1e-')); |
| assertFalse(goog.json.isValid('1e+')); |
| |
| assertFalse(goog.json.isValid('1e-')); |
| |
| assertFalse(goog.json.isValid('"\\\u200D\\"')); |
| assertFalse(goog.json.isValid('"\\\0\\"')); |
| assertFalse(goog.json.isValid('"\\\0"')); |
| assertFalse(goog.json.isValid('"\\0"')); |
| assertFalse(goog.json.isValid('"\x0c"')); |
| |
| assertFalse(goog.json.isValid('"\\\u200D\\", alert(\'foo\') //"\n')); |
| } |
| |
| function testIsValid() { |
| assertTrue(goog.json.isValid('\n""\n')); |
| assertTrue(goog.json.isValid('[1\n,2\r,3\u2028\n,4\u2029]')); |
| assertTrue(goog.json.isValid('"\x7f"')); |
| assertTrue(goog.json.isValid('"\x09"')); |
| // Test tab characters in json. |
| assertTrue(goog.json.isValid('{"\t":"\t"}')); |
| } |
| |
| function testDoNotSerializeProto() { |
| function F() {}; |
| F.prototype = { |
| c: 3 |
| }; |
| |
| var obj = new F; |
| obj.a = 1; |
| obj.b = 2; |
| |
| assertEquals('Should not follow the prototype chain', |
| '{"a":1,"b":2}', |
| goog.json.serialize(obj)); |
| } |
| |
| function testEscape() { |
| var unescaped = '1a*/]'; |
| assertEquals('Should not escape', |
| '"' + unescaped + '"', |
| goog.json.serialize(unescaped)); |
| |
| var escaped = '\n\x7f\u1049'; |
| assertEquals('Should escape', |
| '', |
| findCommonChar(escaped, goog.json.serialize(escaped))); |
| assertEquals('Should eval to the same string after escaping', |
| escaped, |
| goog.json.parse(goog.json.serialize(escaped))); |
| } |
| |
| function testReplacer() { |
| assertSerialize('[null,null,0]', [, , 0]); |
| |
| assertSerialize('[0,0,{"x":0}]', [, , {x: 0}], function(k, v) { |
| if (v === undefined && goog.isArray(this)) { |
| return 0; |
| } |
| return v; |
| }); |
| |
| assertSerialize('[0,1,2,3]', [0, 0, 0, 0], function(k, v) { |
| var kNum = Number(k); |
| if (k && !isNaN(kNum)) { |
| return kNum; |
| } |
| return v; |
| }); |
| |
| var f = function(k, v) { |
| return typeof v == 'number' ? v + 1 : v; |
| }; |
| assertSerialize('{"a":1,"b":{"c":2}}', {'a': 0, 'b': {'c': 1}}, f); |
| } |
| |
| function testDateSerialize() { |
| assertSerialize('{}', new Date(0)); |
| } |
| |
| function testToJSONSerialize() { |
| assertSerialize('{}', {toJSON: goog.functions.constant('serialized')}); |
| assertSerialize('{"toJSON":"normal"}', {toJSON: 'normal'}); |
| } |
| |
| |
| /** |
| * Asserts that the given object serializes to the given value. |
| * If the current browser has an implementation of JSON.serialize, |
| * we make sure our version matches that one. |
| */ |
| function assertSerialize(expected, obj, opt_replacer) { |
| assertEquals(expected, goog.json.serialize(obj, opt_replacer)); |
| |
| // I'm pretty sure that the goog.json.serialize behavior is correct by the ES5 |
| // spec, but JSON.stringify(undefined) is undefined on all browsers. |
| if (obj === undefined) return; |
| |
| // Browsers don't serialize undefined properties, but goog.json.serialize does |
| if (goog.isObject(obj) && ('a' in obj) && obj['a'] === undefined) return; |
| |
| // Replacers are broken on IE and older versions of firefox. |
| if (opt_replacer && !goog.userAgent.WEBKIT) return; |
| |
| // goog.json.serialize does not stringify dates the same way. |
| if (obj instanceof Date) return; |
| |
| // goog.json.serialize does not stringify functions the same way. |
| if (obj instanceof Function) return; |
| |
| // goog.json.serialize doesn't use the toJSON method. |
| if (goog.isObject(obj) && goog.isFunction(obj.toJSON)) return; |
| |
| if (typeof JSON != 'undefined') { |
| assertEquals( |
| 'goog.json.serialize does not match JSON.stringify', |
| expected, |
| JSON.stringify(obj, opt_replacer)); |
| } |
| } |
| |
| |
| /** |
| * @param {string} a |
| * @param {string} b |
| * @return {string} any common character between two strings a and b. |
| */ |
| function findCommonChar(a, b) { |
| for (var i = 0; i < b.length; i++) { |
| if (a.indexOf(b.charAt(i)) >= 0) { |
| return b.charAt(i); |
| } |
| } |
| return ''; |
| } |