prototype: Add support for JSON encoding and decoding. Closes #7427.
This commit is contained in:
parent
f281192758
commit
f160bc4d4d
@ -1,5 +1,7 @@
|
||||
*SVN*
|
||||
|
||||
* Add support for JSON encoding and decoding. Closes #7427. [Tobie Langel]
|
||||
|
||||
* Fix double escaping of query parameters in Hash.prototype.toQueryString, and prevent Safari from iterating over shadowed properties when creating hashes. Closes #7421. [Tobie Langel, Mislav Marohnić]
|
||||
|
||||
* Fix simulated attribute reading for IE for "href", "src" and boolean attributes. [Mislav Marohnić, Thomas Fuchs]
|
||||
|
@ -86,6 +86,15 @@ Object.extend(Array.prototype, {
|
||||
|
||||
inspect: function() {
|
||||
return '[' + this.map(Object.inspect).join(', ') + ']';
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
var results = [];
|
||||
this.each(function(object) {
|
||||
var value = Object.toJSON(object);
|
||||
if (value !== undefined) results.push(value);
|
||||
});
|
||||
return '[' + results.join(',') + ']';
|
||||
}
|
||||
});
|
||||
|
||||
|
42
src/base.js
42
src/base.js
@ -27,6 +27,26 @@ Object.extend(Object, {
|
||||
}
|
||||
},
|
||||
|
||||
toJSON: function(object) {
|
||||
var type = typeof object;
|
||||
switch(type) {
|
||||
case 'undefined':
|
||||
case 'function':
|
||||
case 'unknown': return;
|
||||
case 'boolean': return object.toString();
|
||||
}
|
||||
if (object === null) return 'null';
|
||||
if (object.toJSON) return object.toJSON();
|
||||
if (object.ownerDocument === document) return;
|
||||
var results = [];
|
||||
for (var property in object) {
|
||||
var value = Object.toJSON(object[property]);
|
||||
if (value !== undefined)
|
||||
results.push(property.toJSON() + ':' + value);
|
||||
}
|
||||
return '{' + results.join(',') + '}';
|
||||
},
|
||||
|
||||
keys: function(object) {
|
||||
var keys = [];
|
||||
for (var property in object)
|
||||
@ -62,9 +82,7 @@ Function.prototype.bindAsEventListener = function(object) {
|
||||
|
||||
Object.extend(Number.prototype, {
|
||||
toColorPart: function() {
|
||||
var digits = this.toString(16);
|
||||
if (this < 16) return '0' + digits;
|
||||
return digits;
|
||||
return this.toPaddedString(2, 16);
|
||||
},
|
||||
|
||||
succ: function() {
|
||||
@ -74,9 +92,27 @@ Object.extend(Number.prototype, {
|
||||
times: function(iterator) {
|
||||
$R(0, this, true).each(iterator);
|
||||
return this;
|
||||
},
|
||||
|
||||
toPaddedString: function(length, radix) {
|
||||
var string = this.toString(radix || 10);
|
||||
return '0'.times(length - string.length) + string;
|
||||
},
|
||||
|
||||
toJSON: function(){
|
||||
return isFinite(this) ? this.toString() : 'null';
|
||||
}
|
||||
});
|
||||
|
||||
Date.prototype.toJSON = function() {
|
||||
return '"' + this.getFullYear() + '-' +
|
||||
(this.getMonth() + 1).toPaddedString(2) + '-' +
|
||||
this.getDate().toPaddedString(2) + 'T' +
|
||||
this.getHours().toPaddedString(2) + ':' +
|
||||
this.getMinutes().toPaddedString(2) + ':' +
|
||||
this.getSeconds().toPaddedString(2) + '"';
|
||||
};
|
||||
|
||||
var Try = {
|
||||
these: function() {
|
||||
var returnValue;
|
||||
|
13
src/hash.js
13
src/hash.js
@ -22,6 +22,15 @@ Object.extend(Hash, {
|
||||
});
|
||||
|
||||
return parts.join('&');
|
||||
},
|
||||
|
||||
toJSON: function(object) {
|
||||
var results = [];
|
||||
this.prototype._each.call(object, function(pair){
|
||||
var value = Object.toJSON(pair.value);
|
||||
if (value !== undefined) results.push(pair.key.toJSON() + ':' + value);
|
||||
});
|
||||
return '{' + results.join(',') + '}';
|
||||
}
|
||||
});
|
||||
|
||||
@ -84,6 +93,10 @@ Object.extend(Hash.prototype, {
|
||||
return '#<Hash:{' + this.map(function(pair) {
|
||||
return pair.map(Object.inspect).join(': ');
|
||||
}).join(', ') + '}>';
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return Hash.toJSON(this);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,16 @@
|
||||
String.interpret = function(value) {
|
||||
return value == null ? '' : String(value);
|
||||
}
|
||||
Object.extend(String, {
|
||||
interpret: function(value){
|
||||
return value == null ? '' : String(value);
|
||||
},
|
||||
specialChar: {
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'\\': '\\\\'
|
||||
}
|
||||
});
|
||||
|
||||
Object.extend(String.prototype, {
|
||||
gsub: function(pattern, replacement) {
|
||||
@ -107,7 +117,13 @@ Object.extend(String.prototype, {
|
||||
return this.slice(0, this.length - 1) +
|
||||
String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
|
||||
},
|
||||
|
||||
|
||||
times: function(count) {
|
||||
var result = '';
|
||||
for (var i = 0; i < count; i++) result += this;
|
||||
return result;
|
||||
},
|
||||
|
||||
camelize: function() {
|
||||
var parts = this.split('-'), len = parts.length;
|
||||
if (len == 1) return parts[0];
|
||||
@ -135,13 +151,26 @@ Object.extend(String.prototype, {
|
||||
},
|
||||
|
||||
inspect: function(useDoubleQuotes) {
|
||||
var escapedString = this.replace(/\\/g, '\\\\');
|
||||
if (useDoubleQuotes)
|
||||
return '"' + escapedString.replace(/"/g, '\\"') + '"';
|
||||
else
|
||||
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
|
||||
var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
|
||||
var character = String.specialChar[match[0]];
|
||||
return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
|
||||
});
|
||||
if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
|
||||
return "'" + escapedString.replace(/'/g, '\\\'') + "'";
|
||||
},
|
||||
|
||||
toJSON: function(){
|
||||
return this.inspect(true);
|
||||
},
|
||||
|
||||
evalJSON: function(sanitize){
|
||||
try {
|
||||
if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this)))
|
||||
return eval('(' + this + ')');
|
||||
} catch(e) {}
|
||||
throw new SyntaxError('Badly formated JSON string: ' + this.inspect());
|
||||
},
|
||||
|
||||
include: function(pattern) {
|
||||
return this.indexOf(pattern) > -1;
|
||||
},
|
||||
|
@ -102,6 +102,13 @@
|
||||
assertEqual('[\'a\', 1]',['a',1].inspect());
|
||||
}},
|
||||
|
||||
testToJSON: function(){ with(this) {
|
||||
assertEqual('[]', Object.toJSON([]));
|
||||
assertEqual('[\"a\"]', Object.toJSON(['a']));
|
||||
assertEqual('[\"a\",1]', Object.toJSON(['a', 1]));
|
||||
assertEqual('[\"a\",{\"b\":null}]', Object.toJSON(['a', {'b': null}]));
|
||||
}},
|
||||
|
||||
testReduce: function(){ with(this) {
|
||||
assertUndefined([].reduce());
|
||||
assertNull([null].reduce());
|
||||
|
@ -22,11 +22,17 @@
|
||||
|
||||
<!-- Log output -->
|
||||
<div id="testlog"> </div>
|
||||
|
||||
<div id="test"></div>
|
||||
<!-- Tests follow -->
|
||||
<script type="text/javascript" language="javascript" charset="utf-8">
|
||||
// <![CDATA[
|
||||
|
||||
var Person = function(name){
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
Person.prototype.toJSON = function() {
|
||||
return '-' + this.name;
|
||||
};
|
||||
|
||||
var peEventCount = 0;
|
||||
// peEventFired will stop the PeriodicalExecuter after 3 callbacks
|
||||
@ -87,6 +93,33 @@
|
||||
assertEqual('[]', Object.inspect([]));
|
||||
}},
|
||||
|
||||
testObjectToJSON: function() { with(this) {
|
||||
assertUndefined(Object.toJSON(undefined));
|
||||
assertUndefined(Object.toJSON(Prototype.K));
|
||||
assertEqual('\"\"', Object.toJSON(''));
|
||||
assertEqual('[]', Object.toJSON([]));
|
||||
assertEqual('[\"a\"]', Object.toJSON(['a']));
|
||||
assertEqual('[\"a\",1]', Object.toJSON(['a', 1]));
|
||||
assertEqual('[\"a\",{\"b\":null}]', Object.toJSON(['a', {'b': null}]));
|
||||
assertEqual('{\"a\":\"hello!\"}', Object.toJSON({a: 'hello!'}));
|
||||
assertEqual('{}', Object.toJSON({}));
|
||||
assertEqual('{}', Object.toJSON({a: undefined, b: undefined, c: Prototype.K}));
|
||||
assertEqual('{\"b\":[false,true],\"c\":{\"a\":\"hello!\"}}',
|
||||
Object.toJSON({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}));
|
||||
assertEqual('{\"b\":[false,true],\"c\":{\"a\":\"hello!\"}}',
|
||||
Object.toJSON($H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}})));
|
||||
assertEqual('true', Object.toJSON(true));
|
||||
assertEqual('false', Object.toJSON(false));
|
||||
assertEqual('null', Object.toJSON(null));
|
||||
var sam = new Person('sam');
|
||||
assertEqual('-sam', Object.toJSON(sam));
|
||||
assertEqual('-sam', sam.toJSON());
|
||||
var element = $('test');
|
||||
assertUndefined(Object.toJSON(element));
|
||||
element.toJSON = function(){return 'I\'m a div with id test'};
|
||||
assertEqual('I\'m a div with id test', Object.toJSON(element));
|
||||
}},
|
||||
|
||||
// sanity check
|
||||
testDoesntExtendObjectPrototype: function() {with(this) {
|
||||
// for-in is supported with objects
|
||||
@ -125,6 +158,32 @@
|
||||
}
|
||||
},
|
||||
|
||||
testNumberToColorPart: function() {with(this) {
|
||||
assertEqual('00', (0).toColorPart());
|
||||
assertEqual('0a', (10).toColorPart());
|
||||
assertEqual('ff', (255).toColorPart());
|
||||
}},
|
||||
|
||||
testNumberToPaddedString: function() {with(this) {
|
||||
assertEqual('00', (0).toPaddedString(2, 16));
|
||||
assertEqual('0a', (10).toPaddedString(2, 16));
|
||||
assertEqual('ff', (255).toPaddedString(2, 16));
|
||||
assertEqual('000', (0).toPaddedString(3));
|
||||
assertEqual('010', (10).toPaddedString(3));
|
||||
assertEqual('100', (100).toPaddedString(3));
|
||||
assertEqual('1000', (1000).toPaddedString(3));
|
||||
}},
|
||||
|
||||
testNumberToJSON: function() {with(this) {
|
||||
assertEqual('null', Number.NaN.toJSON());
|
||||
assertEqual('0', (0).toJSON());
|
||||
assertEqual('-293', (-293).toJSON());
|
||||
}},
|
||||
|
||||
testDateToJSON: function() {with(this) {
|
||||
assertEqual('\"1969-12-31T19:00:00\"', new Date(1969, 11, 31, 19).toJSON());
|
||||
}},
|
||||
|
||||
testBrowserDetection: function() {with(this) {
|
||||
var results = $H(Prototype.Browser).map(function(engine){
|
||||
return engine;
|
||||
|
@ -126,6 +126,13 @@
|
||||
assertEqual('#<Hash:{}>', $H({}).inspect());
|
||||
assertEqual("#<Hash:{'a': 'A#'}>", $H(Fixtures.one).inspect());
|
||||
assertEqual("#<Hash:{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D#'}>", $H(Fixtures.many).inspect());
|
||||
}},
|
||||
|
||||
testToJSON: function(){ with(this) {
|
||||
assertEqual('{\"b\":[false,true],\"c\":{\"a\":\"hello!\"}}',
|
||||
$H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}).toJSON());
|
||||
|
||||
assertEqual('E', eval('(' + $H(Fixtures.dangerous).toJSON() + ')')._each);
|
||||
}}
|
||||
}, 'testlog');
|
||||
// ]]>
|
||||
|
@ -26,6 +26,7 @@
|
||||
<!-- Tests follow -->
|
||||
<script type="text/javascript" language="javascript" charset="utf-8">
|
||||
// <![CDATA[
|
||||
var attackTarget;
|
||||
var evalScriptsCounter = 0,
|
||||
largeTextEscaped = '<span>test</span>',
|
||||
largeTextUnescaped = '<span>test</span>';
|
||||
@ -320,6 +321,11 @@
|
||||
assertEqual('\'\'', ''.inspect());
|
||||
assertEqual('\'test\'', 'test'.inspect());
|
||||
assertEqual('\'test \\\'test\\\' "test"\'', 'test \'test\' "test"'.inspect());
|
||||
assertEqual('\"test \'test\' \\"test\\"\"', 'test \'test\' "test"'.inspect(true));
|
||||
assertEqual('\'\\b\\t\\n\\f\\r"\\\\\'', '\b\t\n\f\r"\\'.inspect());
|
||||
assertEqual('\"\\b\\t\\n\\f\\r\\"\\\\\"', '\b\t\n\f\r"\\'.inspect(true));
|
||||
assertEqual('\'\\b\\t\\n\\f\\r\'', '\x08\x09\x0a\x0c\x0d'.inspect());
|
||||
assertEqual('\'\\u001a\'', '\x1a'.inspect());
|
||||
}},
|
||||
|
||||
testInclude: function() {with(this) {
|
||||
@ -369,7 +375,41 @@
|
||||
assertEqual('abce', 'abcd'.succ());
|
||||
assertEqual('{', 'z'.succ());
|
||||
assertEqual(':', '9'.succ());
|
||||
}}
|
||||
}},
|
||||
|
||||
testTimes: function() {with(this) {
|
||||
assertEqual('', ''.times(0));
|
||||
assertEqual('', ''.times(5));
|
||||
assertEqual('', 'a'.times(0));
|
||||
assertEqual('a', 'a'.times(1));
|
||||
assertEqual('aaaaa', 'a'.times(5));
|
||||
assertEqual('foofoofoofoofoo', 'foo'.times(5));
|
||||
assertEqual('', 'foo'.times(-5));
|
||||
}},
|
||||
|
||||
testToJSON: function() {with(this) {
|
||||
assertEqual('\"\"', ''.toJSON());
|
||||
assertEqual('\"test\"', 'test'.toJSON());
|
||||
}},
|
||||
|
||||
testEvalJSON: function() {with(this) {
|
||||
var valid = '{test: "hello world!"}';
|
||||
var invalid = '{test: "hello world!"';
|
||||
var dangerous = '{});attackTarget = "attack succeeded!";({}';
|
||||
|
||||
assertEqual('hello world!', valid.evalJSON().test);
|
||||
assertEqual('hello world!', valid.evalJSON(true).test);
|
||||
assertRaise('SyntaxError', function(){invalid.evalJSON();});
|
||||
assertRaise('SyntaxError', function(){invalid.evalJSON(true);});
|
||||
|
||||
attackTarget = "scared";
|
||||
dangerous.evalJSON();
|
||||
assertEqual("attack succeeded!", attackTarget);
|
||||
|
||||
attackTarget = "Not scared!";
|
||||
assertRaise('SyntaxError', function(){dangerous.evalJSON(true)});
|
||||
assertEqual("Not scared!", attackTarget);
|
||||
}}
|
||||
}, 'testlog');
|
||||
// ]]>
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user