Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit 5a9e1c6

Browse files
committed
Fix issue 60 (constructed/instantiated primitives).
* Added unit tests for constructed Number/Boolean/RegExp, which we did wrong. They were failing when I added them, and fixed it. new Number(7); became: {} new String('hello'); became: {"0":"h","1":"e","2":"l","3":"l","4":"o"} Now fixed. * Also optimized a few code paths for minification - if ( !.. ) { continue; } .. -> if ( .. ) { .. } Saved ~ 150 bytes in the minified version.
1 parent 59974d1 commit 5a9e1c6

File tree

3 files changed

+147
-95
lines changed

3 files changed

+147
-95
lines changed

build/jquery.json.min.js

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/*! jQuery JSON plugin 2.4-alpha | code.google.com/p/jquery-json */
2-
(function($){"use strict";var escapeable=/["\\\x00-\x1f\x7f-\x9f]/g,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},hasOwn=Object.prototype.hasOwnProperty;$.toJSON=typeof JSON==='object'&&JSON.stringify?JSON.stringify:function(o){if(o===null){return'null';}
3-
var pairs,i,k,name,val,type=typeof o,$type=$.type(o);if(type==='undefined'){return undefined;}
2+
(function($){var escape=/["\\\x00-\x1f\x7f-\x9f]/g,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},hasOwn=Object.prototype.hasOwnProperty;$.toJSON=typeof JSON==='object'&&JSON.stringify?JSON.stringify:function(o){if(o===null){return'null';}
3+
var pairs,k,name,val,type=$.type(o);if(type==='undefined'){return undefined;}
44
if(type==='number'||type==='boolean'){return String(o);}
55
if(type==='string'){return $.quoteString(o);}
6-
if(type==='object'){if(typeof o.toJSON==='function'){return $.toJSON(o.toJSON());}
7-
if($type==='date'){var month=o.getUTCMonth()+1,day=o.getUTCDate(),year=o.getUTCFullYear(),hours=o.getUTCHours(),minutes=o.getUTCMinutes(),seconds=o.getUTCSeconds(),milli=o.getUTCMilliseconds();if(month<10){month='0'+month;}
6+
if(typeof o.toJSON==='function'){return $.toJSON(o.toJSON());}
7+
if(type==='date'){var month=o.getUTCMonth()+1,day=o.getUTCDate(),year=o.getUTCFullYear(),hours=o.getUTCHours(),minutes=o.getUTCMinutes(),seconds=o.getUTCSeconds(),milli=o.getUTCMilliseconds();if(month<10){month='0'+month;}
88
if(day<10){day='0'+day;}
99
if(hours<10){hours='0'+hours;}
1010
if(minutes<10){minutes='0'+minutes;}
@@ -13,13 +13,11 @@ if(milli<100){milli='0'+milli;}
1313
if(milli<10){milli='0'+milli;}
1414
return'"'+year+'-'+month+'-'+day+'T'+
1515
hours+':'+minutes+':'+seconds+'.'+milli+'Z"';}
16-
pairs=[];if($.isArray(o)){for(i=0;i<o.length;i++){pairs.push($.toJSON(o[i])||'null');}
16+
pairs=[];if($.isArray(o)){for(k=0;k<o.length;k++){pairs.push($.toJSON(o[k])||'null');}
1717
return'['+pairs.join(',')+']';}
18-
for(k in o){if(!hasOwn.call(o,k)){continue;}
19-
type=typeof k;if(type==='number'){name='"'+k+'"';}else if(type==='string'){name=$.quoteString(k);}else{continue;}
20-
type=typeof o[k];if(type==='function'||type==='undefined'){continue;}
21-
val=$.toJSON(o[k]);pairs.push(name+':'+val);}
22-
return'{'+pairs.join(',')+'}';}};$.evalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(src){return eval('('+src+')');};$.secureEvalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(src){var filtered=src.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered)){return eval('('+src+')');}
23-
throw new SyntaxError('Error parsing JSON, source is not valid.');};$.quoteString=function(string){if(string.match(escapeable)){return'"'+string.replace(escapeable,function(a){var c=meta[a];if(typeof c==='string'){return c;}
18+
if(typeof o==='object'){for(k in o){if(hasOwn.call(o,k)){type=typeof k;if(type==='number'){name='"'+k+'"';}else if(type==='string'){name=$.quoteString(k);}else{continue;}
19+
type=typeof o[k];if(type!=='function'&&type!=='undefined'){val=$.toJSON(o[k]);pairs.push(name+':'+val);}}}
20+
return'{'+pairs.join(',')+'}';}};$.evalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){return eval('('+str+')');};$.secureEvalJSON=typeof JSON==='object'&&JSON.parse?JSON.parse:function(str){var filtered=str.replace(/\\["\\\/bfnrtu]/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered)){return eval('('+str+')');}
21+
throw new SyntaxError('Error parsing JSON, source is not valid.');};$.quoteString=function(str){if(str.match(escape)){return'"'+str.replace(escape,function(a){var c=meta[a];if(typeof c==='string'){return c;}
2422
c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
25-
return'"'+string+'"';};}(jQuery));
23+
return'"'+str+'"';};}(jQuery));

src/jquery.json.js

Lines changed: 86 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
* @license MIT License <http://www.opensource.org/licenses/mit-license.php>
1313
*/
1414
/*global jQuery */
15-
/*jslint continue: true, plusplus: true */
15+
/*jslint sloppy: true, continue: true, plusplus: true */
1616
(function ($) {
17-
"use strict";
18-
19-
var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g,
17+
var escape = /["\\\x00-\x1f\x7f-\x9f]/g,
2018
meta = {
2119
'\b': '\\b',
2220
'\t': '\\t',
@@ -44,124 +42,129 @@
4442
return 'null';
4543
}
4644

47-
var pairs, i, k, name, val,
48-
type = typeof o,
49-
$type = $.type(o);
45+
var pairs, k, name, val,
46+
type = $.type(o);
5047

5148
if (type === 'undefined') {
5249
return undefined;
5350
}
51+
52+
// Also covers instantiated Number and Boolean objects,
53+
// which are typeof 'object' but thanks to $.type, we
54+
// catch them here. I don't know whether it is right
55+
// or wrong that instantiated primitives are not
56+
// exported to JSON as an {"object":..}.
57+
// We choose this path because that's what the browsers did.
5458
if (type === 'number' || type === 'boolean') {
5559
return String(o);
5660
}
5761
if (type === 'string') {
5862
return $.quoteString(o);
5963
}
60-
if (type === 'object') {
61-
if (typeof o.toJSON === 'function') {
62-
return $.toJSON(o.toJSON());
63-
}
64-
if ($type === 'date') {
65-
var month = o.getUTCMonth() + 1,
66-
day = o.getUTCDate(),
67-
year = o.getUTCFullYear(),
68-
hours = o.getUTCHours(),
69-
minutes = o.getUTCMinutes(),
70-
seconds = o.getUTCSeconds(),
71-
milli = o.getUTCMilliseconds();
64+
if (typeof o.toJSON === 'function') {
65+
return $.toJSON(o.toJSON());
66+
}
67+
if (type === 'date') {
68+
var month = o.getUTCMonth() + 1,
69+
day = o.getUTCDate(),
70+
year = o.getUTCFullYear(),
71+
hours = o.getUTCHours(),
72+
minutes = o.getUTCMinutes(),
73+
seconds = o.getUTCSeconds(),
74+
milli = o.getUTCMilliseconds();
7275

73-
if (month < 10) {
74-
month = '0' + month;
75-
}
76-
if (day < 10) {
77-
day = '0' + day;
78-
}
79-
if (hours < 10) {
80-
hours = '0' + hours;
81-
}
82-
if (minutes < 10) {
83-
minutes = '0' + minutes;
84-
}
85-
if (seconds < 10) {
86-
seconds = '0' + seconds;
87-
}
88-
if (milli < 100) {
89-
milli = '0' + milli;
90-
}
91-
if (milli < 10) {
92-
milli = '0' + milli;
93-
}
94-
return '"' + year + '-' + month + '-' + day + 'T' +
95-
hours + ':' + minutes + ':' + seconds +
96-
'.' + milli + 'Z"';
76+
if (month < 10) {
77+
month = '0' + month;
78+
}
79+
if (day < 10) {
80+
day = '0' + day;
9781
}
82+
if (hours < 10) {
83+
hours = '0' + hours;
84+
}
85+
if (minutes < 10) {
86+
minutes = '0' + minutes;
87+
}
88+
if (seconds < 10) {
89+
seconds = '0' + seconds;
90+
}
91+
if (milli < 100) {
92+
milli = '0' + milli;
93+
}
94+
if (milli < 10) {
95+
milli = '0' + milli;
96+
}
97+
return '"' + year + '-' + month + '-' + day + 'T' +
98+
hours + ':' + minutes + ':' + seconds +
99+
'.' + milli + 'Z"';
100+
}
98101

99-
pairs = [];
102+
pairs = [];
100103

101-
if ($.isArray(o)) {
102-
for (i = 0; i < o.length; i++) {
103-
pairs.push($.toJSON(o[i]) || 'null');
104-
}
105-
return '[' + pairs.join(',') + ']';
104+
if ($.isArray(o)) {
105+
for (k = 0; k < o.length; k++) {
106+
pairs.push($.toJSON(o[k]) || 'null');
106107
}
108+
return '[' + pairs.join(',') + ']';
109+
}
107110

108-
// Plain object
111+
// Any other object (plain object, RegExp, ..)
112+
// Need to do typeof instead of $.type, because we also
113+
// want to catch non-plain objects.
114+
if (typeof o === 'object') {
109115
for (k in o) {
110116
// Only include own properties,
111117
// Filter out inherited prototypes
112-
if (!hasOwn.call(o, k)) {
113-
continue;
114-
}
115-
116-
// Keys must be numerical or string. Skip others
117-
type = typeof k;
118-
if (type === 'number') {
119-
name = '"' + k + '"';
120-
} else if (type === 'string') {
121-
name = $.quoteString(k);
122-
} else {
123-
continue;
124-
}
125-
type = typeof o[k];
118+
if (hasOwn.call(o, k)) {
119+
// Keys must be numerical or string. Skip others
120+
type = typeof k;
121+
if (type === 'number') {
122+
name = '"' + k + '"';
123+
} else if (type === 'string') {
124+
name = $.quoteString(k);
125+
} else {
126+
continue;
127+
}
128+
type = typeof o[k];
126129

127-
// Invalid values like these return undefined
128-
// from toJSON, however those object members
129-
// shouldn't be included in the JSON string at all.
130-
if (type === 'function' || type === 'undefined') {
131-
continue;
130+
// Invalid values like these return undefined
131+
// from toJSON, however those object members
132+
// shouldn't be included in the JSON string at all.
133+
if (type !== 'function' && type !== 'undefined') {
134+
val = $.toJSON(o[k]);
135+
pairs.push(name + ':' + val);
136+
}
132137
}
133-
val = $.toJSON(o[k]);
134-
pairs.push(name + ':' + val);
135138
}
136139
return '{' + pairs.join(',') + '}';
137140
}
138141
};
139142

140143
/**
141144
* jQuery.evalJSON
142-
* Evaluates a given piece of json source.
145+
* Evaluates a given json string.
143146
*
144-
* @param src {String}
147+
* @param str {String}
145148
*/
146-
$.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (src) {
147-
return eval('(' + src + ')');
149+
$.evalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
150+
return eval('(' + str + ')');
148151
};
149152

150153
/**
151154
* jQuery.secureEvalJSON
152155
* Evals JSON in a way that is *more* secure.
153156
*
154-
* @param src {String}
157+
* @param str {String}
155158
*/
156-
$.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (src) {
159+
$.secureEvalJSON = typeof JSON === 'object' && JSON.parse ? JSON.parse : function (str) {
157160
var filtered =
158-
src
161+
str
159162
.replace(/\\["\\\/bfnrtu]/g, '@')
160163
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
161164
.replace(/(?:^|:|,)(?:\s*\[)+/g, '');
162165

163166
if (/^[\],:{}\s]*$/.test(filtered)) {
164-
return eval('(' + src + ')');
167+
return eval('(' + str + ')');
165168
}
166169
throw new SyntaxError('Error parsing JSON, source is not valid.');
167170
};
@@ -177,9 +180,9 @@
177180
* >>> jQuery.quoteString('"Where are we going?", she asked.')
178181
* "\"Where are we going?\", she asked."
179182
*/
180-
$.quoteString = function (string) {
181-
if (string.match(escapeable)) {
182-
return '"' + string.replace(escapeable, function (a) {
183+
$.quoteString = function (str) {
184+
if (str.match(escape)) {
185+
return '"' + str.replace(escape, function (a) {
183186
var c = meta[a];
184187
if (typeof c === 'string') {
185188
return c;
@@ -188,7 +191,7 @@
188191
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
189192
}) + '"';
190193
}
191-
return '"' + string + '"';
194+
return '"' + str + '"';
192195
};
193196

194197
}(jQuery));

test/jquery.json.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,57 @@ QUnit.test('Dates', function (assert) {
134134
);
135135
});
136136

137+
QUnit.test('RegExp', function (assert) {
138+
// We don't really care what RegExp objects end up like,
139+
// but we want to match browser behavior.
140+
assert.toJSON(
141+
new RegExp( 'hello' ),
142+
'{}',
143+
'Instantiated RegExp (simple) '
144+
);
145+
assert.toJSON(
146+
new RegExp( 'hello', 'gi' ),
147+
'{}',
148+
'Instantiated RegExp (with options)'
149+
);
150+
assert.toJSON(
151+
/hello/,
152+
'{}',
153+
'RegExp literal (simple)'
154+
);
155+
assert.toJSON(
156+
/hello/gi,
157+
'{}',
158+
'RegExp literal (with options)'
159+
);
160+
});
161+
162+
QUnit.test('Primitive constructors', function (assert) {
163+
// Nobody should be using new Number(), new Boolean(), or new String()
164+
// but they are an interesting edge case, because they are typeof 'object'.
165+
166+
assert.toJSON(
167+
new Number(7),
168+
'7',
169+
'Instantiated Number'
170+
);
171+
assert.toJSON(
172+
new Boolean(true),
173+
'true',
174+
'Instantiated Boolean (true)'
175+
);
176+
assert.toJSON(
177+
new Boolean(false),
178+
'false',
179+
'Instantiated Boolean (false)'
180+
);
181+
assert.toJSON(
182+
new String('hello'),
183+
'"hello"',
184+
'Instantiated String'
185+
);
186+
});
187+
137188
QUnit.test('Function arguments object', function (assert) {
138189
function argTest() {
139190
assert.toJSON(

0 commit comments

Comments
 (0)