Skip to content

Commit 35125d2

Browse files
committed
refactor(toJson): use native JSON.stringify
Instead of using our custom serializer we now use the native one and use the replacer function to customize the serialization to preserve most of the previous behavior (ignore $ and $$ properties as well as window, document and scope instances).
1 parent 87f5c6e commit 35125d2

File tree

5 files changed

+51
-247
lines changed

5 files changed

+51
-247
lines changed

src/JSON.js

+16-133
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
'use strict';
22

3+
var jsonReplacer = function(key, value) {
4+
var val = value;
5+
if (/^\$+/.test(key)) {
6+
val = undefined;
7+
} else if (isWindow(value)) {
8+
val = '$WINDOW';
9+
} else if (value && document === value) {
10+
val = '$DOCUMENT';
11+
} else if (isScope(value)) {
12+
val = '$SCOPE';
13+
}
14+
15+
return val;
16+
};
17+
318
/**
419
* @ngdoc function
520
* @name angular.toJson
@@ -13,9 +28,7 @@
1328
* @returns {string} Jsonified string representing `obj`.
1429
*/
1530
function toJson(obj, pretty) {
16-
var buf = [];
17-
toJsonArray(buf, obj, pretty ? "\n " : null, []);
18-
return buf.join('');
31+
return JSON.stringify(obj, jsonReplacer, pretty ? ' ' : null);
1932
}
2033

2134
/**
@@ -34,133 +47,3 @@ function fromJson(json) {
3447
? JSON.parse(json)
3548
: json;
3649
}
37-
38-
39-
function jsonDateToString(date){
40-
if (!date) return date;
41-
var isoString = date.toISOString ? date.toISOString() : '';
42-
return (isoString.length==24)
43-
? isoString
44-
: padNumber(date.getUTCFullYear(), 4) + '-' +
45-
padNumber(date.getUTCMonth() + 1, 2) + '-' +
46-
padNumber(date.getUTCDate(), 2) + 'T' +
47-
padNumber(date.getUTCHours(), 2) + ':' +
48-
padNumber(date.getUTCMinutes(), 2) + ':' +
49-
padNumber(date.getUTCSeconds(), 2) + '.' +
50-
padNumber(date.getUTCMilliseconds(), 3) + 'Z';
51-
}
52-
53-
function quoteUnicode(string) {
54-
var chars = ['"'];
55-
for ( var i = 0; i < string.length; i++) {
56-
var code = string.charCodeAt(i);
57-
var ch = string.charAt(i);
58-
switch(ch) {
59-
case '"': chars.push('\\"'); break;
60-
case '\\': chars.push('\\\\'); break;
61-
case '\n': chars.push('\\n'); break;
62-
case '\f': chars.push('\\f'); break;
63-
case '\r': chars.push(ch = '\\r'); break;
64-
case '\t': chars.push(ch = '\\t'); break;
65-
default:
66-
if (32 <= code && code <= 126) {
67-
chars.push(ch);
68-
} else {
69-
var encode = "000" + code.toString(16);
70-
chars.push("\\u" + encode.substring(encode.length - 4));
71-
}
72-
}
73-
}
74-
chars.push('"');
75-
return chars.join('');
76-
}
77-
78-
79-
function toJsonArray(buf, obj, pretty, stack) {
80-
if (isObject(obj)) {
81-
if (obj === window) {
82-
buf.push('WINDOW');
83-
return;
84-
}
85-
86-
if (obj === document) {
87-
buf.push('DOCUMENT');
88-
return;
89-
}
90-
91-
if (includes(stack, obj)) {
92-
buf.push('RECURSION');
93-
return;
94-
}
95-
stack.push(obj);
96-
}
97-
if (obj === null) {
98-
buf.push('null');
99-
} else if (obj instanceof RegExp) {
100-
buf.push(quoteUnicode(obj.toString()));
101-
} else if (isFunction(obj)) {
102-
return;
103-
} else if (isBoolean(obj)) {
104-
buf.push('' + obj);
105-
} else if (isNumber(obj)) {
106-
if (isNaN(obj)) {
107-
buf.push('null');
108-
} else {
109-
buf.push('' + obj);
110-
}
111-
} else if (isString(obj)) {
112-
return buf.push(quoteUnicode(obj));
113-
} else if (isObject(obj)) {
114-
if (isArray(obj)) {
115-
buf.push("[");
116-
var len = obj.length;
117-
var sep = false;
118-
for(var i=0; i<len; i++) {
119-
var item = obj[i];
120-
if (sep) buf.push(",");
121-
if (!(item instanceof RegExp) && (isFunction(item) || isUndefined(item))) {
122-
buf.push('null');
123-
} else {
124-
toJsonArray(buf, item, pretty, stack);
125-
}
126-
sep = true;
127-
}
128-
buf.push("]");
129-
} else if (isElement(obj)) {
130-
// TODO(misko): maybe in dev mode have a better error reporting?
131-
buf.push('DOM_ELEMENT');
132-
} else if (isDate(obj)) {
133-
buf.push(quoteUnicode(jsonDateToString(obj)));
134-
} else {
135-
buf.push("{");
136-
if (pretty) buf.push(pretty);
137-
var comma = false;
138-
var childPretty = pretty ? pretty + " " : false;
139-
var keys = [];
140-
for(var k in obj) {
141-
if (k!='this' && k!='$parent' && k.substring(0,2) != '$$' && obj.hasOwnProperty(k) && obj[k] !== undefined) {
142-
keys.push(k);
143-
}
144-
}
145-
keys.sort();
146-
for ( var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
147-
var key = keys[keyIndex];
148-
var value = obj[key];
149-
if (!isFunction(value)) {
150-
if (comma) {
151-
buf.push(",");
152-
if (pretty) buf.push(pretty);
153-
}
154-
buf.push(quoteUnicode(key));
155-
buf.push(":");
156-
toJsonArray(buf, value, childPretty, stack);
157-
comma = true;
158-
}
159-
}
160-
buf.push("}");
161-
}
162-
}
163-
if (isObject(obj)) {
164-
stack.pop();
165-
}
166-
}

src/ng/directive/input.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
671671
</doc:source>
672672
<doc:scenario>
673673
it('should initialize to model', function() {
674-
expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}');
674+
expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
675675
expect(binding('myForm.userName.$valid')).toEqual('true');
676676
expect(binding('myForm.$valid')).toEqual('true');
677677
});
@@ -685,7 +685,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
685685
686686
it('should be valid if empty when min length is set', function() {
687687
input('user.last').enter('');
688-
expect(binding('user')).toEqual('{"last":"","name":"guest"}');
688+
expect(binding('user')).toEqual('{"name":"guest","last":""}');
689689
expect(binding('myForm.lastName.$valid')).toEqual('true');
690690
expect(binding('myForm.$valid')).toEqual('true');
691691
});

src/ng/filter/filters.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ function dateFilter($locale) {
407407
</doc:source>
408408
<doc:scenario>
409409
it('should jsonify filtered objects', function() {
410-
expect(binding("{'name':'value'}")).toBe('{\n "name":"value"}');
410+
expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
411411
});
412412
</doc:scenario>
413413
</doc:example>

src/ng/filter/limitTo.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,24 @@
3131
}
3232
</script>
3333
<div ng-controller="Ctrl">
34-
Limit {{numbers}} to: <input type="integer" ng-model="limit"/>
35-
<p>Output: {{ numbers | limitTo:limit | json }}</p>
34+
Limit {{numbers}} to: <input type="integer" ng-model="limit" ng-model-instant>
35+
<p>Output: {{ numbers | limitTo:limit }}</p>
3636
</div>
3737
</doc:source>
3838
<doc:scenario>
3939
it('should limit the numer array to first three items', function() {
4040
expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
41-
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3]');
41+
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
4242
});
4343
4444
it('should update the output when -3 is entered', function() {
4545
input('limit').enter(-3);
46-
expect(binding('numbers | limitTo:limit | json')).toEqual('[7,8,9]');
46+
expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
4747
});
4848
4949
it('should not exceed the maximum size of input array', function() {
5050
input('limit').enter(100);
51-
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3,4,5,6,7,8,9]');
51+
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
5252
});
5353
</doc:scenario>
5454
</doc:example>

test/JsonSpec.js

+27-106
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ describe('json', function() {
44

55
describe('fromJson', function() {
66

7-
it('should delegate to native parser', function() {
7+
it('should delegate to JSON.parse', function() {
88
var spy = spyOn(JSON, 'parse').andCallThrough();
99

1010
expect(fromJson('{}')).toEqual({});
@@ -13,125 +13,46 @@ describe('json', function() {
1313
});
1414

1515

16-
it('should serialize primitives', function() {
17-
expect(toJson(0/0)).toEqual('null');
18-
expect(toJson(null)).toEqual('null');
19-
expect(toJson(true)).toEqual('true');
20-
expect(toJson(false)).toEqual('false');
21-
expect(toJson(123.45)).toEqual('123.45');
22-
expect(toJson('abc')).toEqual('"abc"');
23-
expect(toJson('a \t \n \r b \\')).toEqual('"a \\t \\n \\r b \\\\"');
24-
});
25-
26-
it('should not serialize $$properties', function() {
27-
expect(toJson({$$some:'value', 'this':1, '$parent':1}, false)).toEqual('{}');
28-
});
29-
30-
it('should not serialize this or $parent', function() {
31-
expect(toJson({'this':'value', $parent:'abc'}, false)).toEqual('{}');
32-
});
16+
describe('toJson', function() {
3317

34-
it('should serialize strings with escaped characters', function() {
35-
expect(toJson("7\\\"7")).toEqual("\"7\\\\\\\"7\"");
36-
});
37-
38-
it('should serialize objects', function() {
39-
expect(toJson({a: 1, b: 2})).toEqual('{"a":1,"b":2}');
40-
expect(toJson({a: {b: 2}})).toEqual('{"a":{"b":2}}');
41-
expect(toJson({a: {b: {c: 0}}})).toEqual('{"a":{"b":{"c":0}}}');
42-
expect(toJson({a: {b: 0/0}})).toEqual('{"a":{"b":null}}');
43-
});
44-
45-
it('should format objects pretty', function() {
46-
expect(toJson({a: 1, b: 2}, true)).toEqual('{\n "a":1,\n "b":2}');
47-
expect(toJson({a: {b: 2}}, true)).toEqual('{\n "a":{\n "b":2}}');
48-
});
18+
it('should delegate to JSON.stringify', function() {
19+
var spy = spyOn(JSON, 'stringify').andCallThrough();
4920

50-
it('should serialize array', function() {
51-
expect(toJson([])).toEqual('[]');
52-
expect(toJson([1, 'b'])).toEqual('[1,"b"]');
53-
});
54-
55-
it('should serialize RegExp', function() {
56-
expect(toJson(/foo/)).toEqual('"/foo/"');
57-
expect(toJson([1, new RegExp('foo')])).toEqual('[1,"/foo/"]');
58-
});
59-
60-
it('should ignore functions', function() {
61-
expect(toJson([function() {},1])).toEqual('[null,1]');
62-
expect(toJson({a:function() {}})).toEqual('{}');
63-
});
64-
65-
it('should serialize array with empty items', function() {
66-
var a = [];
67-
a[1] = 'X';
68-
expect(toJson(a)).toEqual('[null,"X"]');
69-
});
70-
71-
it('should escape unicode', function() {
72-
expect('\u00a0'.length).toEqual(1);
73-
expect(toJson('\u00a0').length).toEqual(8);
74-
expect(fromJson(toJson('\u00a0')).length).toEqual(1);
75-
});
76-
77-
it('should serialize UTC dates', function() {
78-
var date = new angular.mock.TzDate(-1, '2009-10-09T01:02:03.027Z');
79-
expect(toJson(date)).toEqual('"2009-10-09T01:02:03.027Z"');
80-
});
81-
82-
it('should prevent recursion', function() {
83-
var obj = {a: 'b'};
84-
obj.recursion = obj;
85-
expect(angular.toJson(obj)).toEqual('{"a":"b","recursion":RECURSION}');
86-
});
21+
expect(toJson({})).toEqual('{}');
22+
expect(spy).toHaveBeenCalled();
23+
});
8724

88-
it('should serialize $ properties', function() {
89-
var obj = {$a: 'a'};
90-
expect(angular.toJson(obj)).toEqual('{"$a":"a"}');
91-
});
9225

93-
it('should NOT serialize inherited properties', function() {
94-
// This is what native Browser does
95-
var obj = inherit({p:'p'});
96-
obj.a = 'a';
97-
expect(angular.toJson(obj)).toEqual('{"a":"a"}');
98-
});
26+
it('should format objects pretty', function() {
27+
expect(toJson({a: 1, b: 2}, true)).
28+
toBeOneOf('{\n "a": 1,\n "b": 2\n}', '{\n "a":1,\n "b":2\n}');
29+
expect(toJson({a: {b: 2}}, true)).
30+
toBeOneOf('{\n "a": {\n "b": 2\n }\n}', '{\n "a":{\n "b":2\n }\n}');
31+
});
9932

100-
it('should serialize same objects multiple times', function() {
101-
var obj = {a:'b'};
102-
expect(angular.toJson({A:obj, B:obj})).toEqual('{"A":{"a":"b"},"B":{"a":"b"}}');
103-
});
10433

105-
it('should not serialize undefined values', function() {
106-
expect(angular.toJson({A:undefined})).toEqual('{}');
107-
});
34+
it('should not serialize properties starting with $', function() {
35+
expect(toJson({$few: 'v', $$some:'value'}, false)).toEqual('{}');
36+
});
10837

109-
it('should not serialize $window object', function() {
110-
expect(toJson(window)).toEqual('WINDOW');
111-
});
11238

113-
it('should not serialize $document object', function() {
114-
expect(toJson(document)).toEqual('DOCUMENT');
115-
});
39+
it('should not serialize undefined values', function() {
40+
expect(angular.toJson({A:undefined})).toEqual('{}');
41+
});
11642

11743

118-
describe('string', function() {
119-
it('should quote', function() {
120-
expect(quoteUnicode('a')).toBe('"a"');
121-
expect(quoteUnicode('\\')).toBe('"\\\\"');
122-
expect(quoteUnicode("'a'")).toBe('"\'a\'"');
123-
expect(quoteUnicode('"a"')).toBe('"\\"a\\""');
124-
expect(quoteUnicode('\n\f\r\t')).toBe('"\\n\\f\\r\\t"');
44+
it('should not serialize $window object', function() {
45+
expect(toJson(window)).toEqual('"$WINDOW"');
12546
});
12647

127-
it('should quote slashes', function() {
128-
expect(quoteUnicode("7\\\"7")).toBe('"7\\\\\\\"7"');
129-
});
13048

131-
it('should quote unicode', function() {
132-
expect(quoteUnicode('abc\u00A0def')).toBe('"abc\\u00a0def"');
49+
it('should not serialize $document object', function() {
50+
expect(toJson(document)).toEqual('"$DOCUMENT"');
13351
});
13452

135-
});
13653

54+
it('should not serialize scope instances', inject(function($rootScope) {
55+
expect(toJson({key: $rootScope})).toEqual('{"key":"$SCOPE"}');
56+
}));
57+
});
13758
});

0 commit comments

Comments
 (0)