diff --git a/CHANGELOG b/CHANGELOG index 95c1d93..859852d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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] diff --git a/src/array.js b/src/array.js index 9271f6a..fd0a427 100644 --- a/src/array.js +++ b/src/array.js @@ -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(',') + ']'; } }); diff --git a/src/base.js b/src/base.js index cf2f438..d0d7535 100644 --- a/src/base.js +++ b/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; diff --git a/src/hash.js b/src/hash.js index 642d6bc..efeb3d1 100644 --- a/src/hash.js +++ b/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 '#'; + }, + + toJSON: function() { + return Hash.toJSON(this); } }); diff --git a/src/string.js b/src/string.js index 5a575e0..c81e45a 100644 --- a/src/string.js +++ b/src/string.js @@ -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; }, diff --git a/test/unit/array.html b/test/unit/array.html index f8518ff..171c9d2 100644 --- a/test/unit/array.html +++ b/test/unit/array.html @@ -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()); diff --git a/test/unit/base.html b/test/unit/base.html index c5e7ddf..4dc1cf8 100644 --- a/test/unit/base.html +++ b/test/unit/base.html @@ -22,11 +22,17 @@
- +