mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
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).
This commit is contained in:
parent
87f5c6e5b7
commit
35125d2513
5 changed files with 58 additions and 254 deletions
149
src/JSON.js
149
src/JSON.js
|
|
@ -1,5 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
var jsonReplacer = function(key, value) {
|
||||
var val = value;
|
||||
if (/^\$+/.test(key)) {
|
||||
val = undefined;
|
||||
} else if (isWindow(value)) {
|
||||
val = '$WINDOW';
|
||||
} else if (value && document === value) {
|
||||
val = '$DOCUMENT';
|
||||
} else if (isScope(value)) {
|
||||
val = '$SCOPE';
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name angular.toJson
|
||||
|
|
@ -13,9 +28,7 @@
|
|||
* @returns {string} Jsonified string representing `obj`.
|
||||
*/
|
||||
function toJson(obj, pretty) {
|
||||
var buf = [];
|
||||
toJsonArray(buf, obj, pretty ? "\n " : null, []);
|
||||
return buf.join('');
|
||||
return JSON.stringify(obj, jsonReplacer, pretty ? ' ' : null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -34,133 +47,3 @@ function fromJson(json) {
|
|||
? JSON.parse(json)
|
||||
: json;
|
||||
}
|
||||
|
||||
|
||||
function jsonDateToString(date){
|
||||
if (!date) return date;
|
||||
var isoString = date.toISOString ? date.toISOString() : '';
|
||||
return (isoString.length==24)
|
||||
? isoString
|
||||
: padNumber(date.getUTCFullYear(), 4) + '-' +
|
||||
padNumber(date.getUTCMonth() + 1, 2) + '-' +
|
||||
padNumber(date.getUTCDate(), 2) + 'T' +
|
||||
padNumber(date.getUTCHours(), 2) + ':' +
|
||||
padNumber(date.getUTCMinutes(), 2) + ':' +
|
||||
padNumber(date.getUTCSeconds(), 2) + '.' +
|
||||
padNumber(date.getUTCMilliseconds(), 3) + 'Z';
|
||||
}
|
||||
|
||||
function quoteUnicode(string) {
|
||||
var chars = ['"'];
|
||||
for ( var i = 0; i < string.length; i++) {
|
||||
var code = string.charCodeAt(i);
|
||||
var ch = string.charAt(i);
|
||||
switch(ch) {
|
||||
case '"': chars.push('\\"'); break;
|
||||
case '\\': chars.push('\\\\'); break;
|
||||
case '\n': chars.push('\\n'); break;
|
||||
case '\f': chars.push('\\f'); break;
|
||||
case '\r': chars.push(ch = '\\r'); break;
|
||||
case '\t': chars.push(ch = '\\t'); break;
|
||||
default:
|
||||
if (32 <= code && code <= 126) {
|
||||
chars.push(ch);
|
||||
} else {
|
||||
var encode = "000" + code.toString(16);
|
||||
chars.push("\\u" + encode.substring(encode.length - 4));
|
||||
}
|
||||
}
|
||||
}
|
||||
chars.push('"');
|
||||
return chars.join('');
|
||||
}
|
||||
|
||||
|
||||
function toJsonArray(buf, obj, pretty, stack) {
|
||||
if (isObject(obj)) {
|
||||
if (obj === window) {
|
||||
buf.push('WINDOW');
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj === document) {
|
||||
buf.push('DOCUMENT');
|
||||
return;
|
||||
}
|
||||
|
||||
if (includes(stack, obj)) {
|
||||
buf.push('RECURSION');
|
||||
return;
|
||||
}
|
||||
stack.push(obj);
|
||||
}
|
||||
if (obj === null) {
|
||||
buf.push('null');
|
||||
} else if (obj instanceof RegExp) {
|
||||
buf.push(quoteUnicode(obj.toString()));
|
||||
} else if (isFunction(obj)) {
|
||||
return;
|
||||
} else if (isBoolean(obj)) {
|
||||
buf.push('' + obj);
|
||||
} else if (isNumber(obj)) {
|
||||
if (isNaN(obj)) {
|
||||
buf.push('null');
|
||||
} else {
|
||||
buf.push('' + obj);
|
||||
}
|
||||
} else if (isString(obj)) {
|
||||
return buf.push(quoteUnicode(obj));
|
||||
} else if (isObject(obj)) {
|
||||
if (isArray(obj)) {
|
||||
buf.push("[");
|
||||
var len = obj.length;
|
||||
var sep = false;
|
||||
for(var i=0; i<len; i++) {
|
||||
var item = obj[i];
|
||||
if (sep) buf.push(",");
|
||||
if (!(item instanceof RegExp) && (isFunction(item) || isUndefined(item))) {
|
||||
buf.push('null');
|
||||
} else {
|
||||
toJsonArray(buf, item, pretty, stack);
|
||||
}
|
||||
sep = true;
|
||||
}
|
||||
buf.push("]");
|
||||
} else if (isElement(obj)) {
|
||||
// TODO(misko): maybe in dev mode have a better error reporting?
|
||||
buf.push('DOM_ELEMENT');
|
||||
} else if (isDate(obj)) {
|
||||
buf.push(quoteUnicode(jsonDateToString(obj)));
|
||||
} else {
|
||||
buf.push("{");
|
||||
if (pretty) buf.push(pretty);
|
||||
var comma = false;
|
||||
var childPretty = pretty ? pretty + " " : false;
|
||||
var keys = [];
|
||||
for(var k in obj) {
|
||||
if (k!='this' && k!='$parent' && k.substring(0,2) != '$$' && obj.hasOwnProperty(k) && obj[k] !== undefined) {
|
||||
keys.push(k);
|
||||
}
|
||||
}
|
||||
keys.sort();
|
||||
for ( var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
|
||||
var key = keys[keyIndex];
|
||||
var value = obj[key];
|
||||
if (!isFunction(value)) {
|
||||
if (comma) {
|
||||
buf.push(",");
|
||||
if (pretty) buf.push(pretty);
|
||||
}
|
||||
buf.push(quoteUnicode(key));
|
||||
buf.push(":");
|
||||
toJsonArray(buf, value, childPretty, stack);
|
||||
comma = true;
|
||||
}
|
||||
}
|
||||
buf.push("}");
|
||||
}
|
||||
}
|
||||
if (isObject(obj)) {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -671,7 +671,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
|
|||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should initialize to model', function() {
|
||||
expect(binding('user')).toEqual('{"last":"visitor","name":"guest"}');
|
||||
expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
|
||||
expect(binding('myForm.userName.$valid')).toEqual('true');
|
||||
expect(binding('myForm.$valid')).toEqual('true');
|
||||
});
|
||||
|
|
@ -685,7 +685,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
|
|||
|
||||
it('should be valid if empty when min length is set', function() {
|
||||
input('user.last').enter('');
|
||||
expect(binding('user')).toEqual('{"last":"","name":"guest"}');
|
||||
expect(binding('user')).toEqual('{"name":"guest","last":""}');
|
||||
expect(binding('myForm.lastName.$valid')).toEqual('true');
|
||||
expect(binding('myForm.$valid')).toEqual('true');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -407,7 +407,7 @@ function dateFilter($locale) {
|
|||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should jsonify filtered objects', function() {
|
||||
expect(binding("{'name':'value'}")).toBe('{\n "name":"value"}');
|
||||
expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
|
|
|||
|
|
@ -31,24 +31,24 @@
|
|||
}
|
||||
</script>
|
||||
<div ng-controller="Ctrl">
|
||||
Limit {{numbers}} to: <input type="integer" ng-model="limit"/>
|
||||
<p>Output: {{ numbers | limitTo:limit | json }}</p>
|
||||
Limit {{numbers}} to: <input type="integer" ng-model="limit" ng-model-instant>
|
||||
<p>Output: {{ numbers | limitTo:limit }}</p>
|
||||
</div>
|
||||
</doc:source>
|
||||
<doc:scenario>
|
||||
it('should limit the numer array to first three items', function() {
|
||||
expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
|
||||
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3]');
|
||||
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]');
|
||||
});
|
||||
|
||||
it('should update the output when -3 is entered', function() {
|
||||
input('limit').enter(-3);
|
||||
expect(binding('numbers | limitTo:limit | json')).toEqual('[7,8,9]');
|
||||
expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]');
|
||||
});
|
||||
|
||||
it('should not exceed the maximum size of input array', function() {
|
||||
input('limit').enter(100);
|
||||
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3,4,5,6,7,8,9]');
|
||||
expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]');
|
||||
});
|
||||
</doc:scenario>
|
||||
</doc:example>
|
||||
|
|
|
|||
147
test/JsonSpec.js
147
test/JsonSpec.js
|
|
@ -4,7 +4,7 @@ describe('json', function() {
|
|||
|
||||
describe('fromJson', function() {
|
||||
|
||||
it('should delegate to native parser', function() {
|
||||
it('should delegate to JSON.parse', function() {
|
||||
var spy = spyOn(JSON, 'parse').andCallThrough();
|
||||
|
||||
expect(fromJson('{}')).toEqual({});
|
||||
|
|
@ -13,125 +13,46 @@ describe('json', function() {
|
|||
});
|
||||
|
||||
|
||||
it('should serialize primitives', function() {
|
||||
expect(toJson(0/0)).toEqual('null');
|
||||
expect(toJson(null)).toEqual('null');
|
||||
expect(toJson(true)).toEqual('true');
|
||||
expect(toJson(false)).toEqual('false');
|
||||
expect(toJson(123.45)).toEqual('123.45');
|
||||
expect(toJson('abc')).toEqual('"abc"');
|
||||
expect(toJson('a \t \n \r b \\')).toEqual('"a \\t \\n \\r b \\\\"');
|
||||
});
|
||||
describe('toJson', function() {
|
||||
|
||||
it('should not serialize $$properties', function() {
|
||||
expect(toJson({$$some:'value', 'this':1, '$parent':1}, false)).toEqual('{}');
|
||||
});
|
||||
it('should delegate to JSON.stringify', function() {
|
||||
var spy = spyOn(JSON, 'stringify').andCallThrough();
|
||||
|
||||
it('should not serialize this or $parent', function() {
|
||||
expect(toJson({'this':'value', $parent:'abc'}, false)).toEqual('{}');
|
||||
});
|
||||
|
||||
it('should serialize strings with escaped characters', function() {
|
||||
expect(toJson("7\\\"7")).toEqual("\"7\\\\\\\"7\"");
|
||||
});
|
||||
|
||||
it('should serialize objects', function() {
|
||||
expect(toJson({a: 1, b: 2})).toEqual('{"a":1,"b":2}');
|
||||
expect(toJson({a: {b: 2}})).toEqual('{"a":{"b":2}}');
|
||||
expect(toJson({a: {b: {c: 0}}})).toEqual('{"a":{"b":{"c":0}}}');
|
||||
expect(toJson({a: {b: 0/0}})).toEqual('{"a":{"b":null}}');
|
||||
});
|
||||
|
||||
it('should format objects pretty', function() {
|
||||
expect(toJson({a: 1, b: 2}, true)).toEqual('{\n "a":1,\n "b":2}');
|
||||
expect(toJson({a: {b: 2}}, true)).toEqual('{\n "a":{\n "b":2}}');
|
||||
});
|
||||
|
||||
it('should serialize array', function() {
|
||||
expect(toJson([])).toEqual('[]');
|
||||
expect(toJson([1, 'b'])).toEqual('[1,"b"]');
|
||||
});
|
||||
|
||||
it('should serialize RegExp', function() {
|
||||
expect(toJson(/foo/)).toEqual('"/foo/"');
|
||||
expect(toJson([1, new RegExp('foo')])).toEqual('[1,"/foo/"]');
|
||||
});
|
||||
|
||||
it('should ignore functions', function() {
|
||||
expect(toJson([function() {},1])).toEqual('[null,1]');
|
||||
expect(toJson({a:function() {}})).toEqual('{}');
|
||||
});
|
||||
|
||||
it('should serialize array with empty items', function() {
|
||||
var a = [];
|
||||
a[1] = 'X';
|
||||
expect(toJson(a)).toEqual('[null,"X"]');
|
||||
});
|
||||
|
||||
it('should escape unicode', function() {
|
||||
expect('\u00a0'.length).toEqual(1);
|
||||
expect(toJson('\u00a0').length).toEqual(8);
|
||||
expect(fromJson(toJson('\u00a0')).length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should serialize UTC dates', function() {
|
||||
var date = new angular.mock.TzDate(-1, '2009-10-09T01:02:03.027Z');
|
||||
expect(toJson(date)).toEqual('"2009-10-09T01:02:03.027Z"');
|
||||
});
|
||||
|
||||
it('should prevent recursion', function() {
|
||||
var obj = {a: 'b'};
|
||||
obj.recursion = obj;
|
||||
expect(angular.toJson(obj)).toEqual('{"a":"b","recursion":RECURSION}');
|
||||
});
|
||||
|
||||
it('should serialize $ properties', function() {
|
||||
var obj = {$a: 'a'};
|
||||
expect(angular.toJson(obj)).toEqual('{"$a":"a"}');
|
||||
});
|
||||
|
||||
it('should NOT serialize inherited properties', function() {
|
||||
// This is what native Browser does
|
||||
var obj = inherit({p:'p'});
|
||||
obj.a = 'a';
|
||||
expect(angular.toJson(obj)).toEqual('{"a":"a"}');
|
||||
});
|
||||
|
||||
it('should serialize same objects multiple times', function() {
|
||||
var obj = {a:'b'};
|
||||
expect(angular.toJson({A:obj, B:obj})).toEqual('{"A":{"a":"b"},"B":{"a":"b"}}');
|
||||
});
|
||||
|
||||
it('should not serialize undefined values', function() {
|
||||
expect(angular.toJson({A:undefined})).toEqual('{}');
|
||||
});
|
||||
|
||||
it('should not serialize $window object', function() {
|
||||
expect(toJson(window)).toEqual('WINDOW');
|
||||
});
|
||||
|
||||
it('should not serialize $document object', function() {
|
||||
expect(toJson(document)).toEqual('DOCUMENT');
|
||||
});
|
||||
|
||||
|
||||
describe('string', function() {
|
||||
it('should quote', function() {
|
||||
expect(quoteUnicode('a')).toBe('"a"');
|
||||
expect(quoteUnicode('\\')).toBe('"\\\\"');
|
||||
expect(quoteUnicode("'a'")).toBe('"\'a\'"');
|
||||
expect(quoteUnicode('"a"')).toBe('"\\"a\\""');
|
||||
expect(quoteUnicode('\n\f\r\t')).toBe('"\\n\\f\\r\\t"');
|
||||
expect(toJson({})).toEqual('{}');
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should quote slashes', function() {
|
||||
expect(quoteUnicode("7\\\"7")).toBe('"7\\\\\\\"7"');
|
||||
|
||||
it('should format objects pretty', function() {
|
||||
expect(toJson({a: 1, b: 2}, true)).
|
||||
toBeOneOf('{\n "a": 1,\n "b": 2\n}', '{\n "a":1,\n "b":2\n}');
|
||||
expect(toJson({a: {b: 2}}, true)).
|
||||
toBeOneOf('{\n "a": {\n "b": 2\n }\n}', '{\n "a":{\n "b":2\n }\n}');
|
||||
});
|
||||
|
||||
it('should quote unicode', function() {
|
||||
expect(quoteUnicode('abc\u00A0def')).toBe('"abc\\u00a0def"');
|
||||
|
||||
it('should not serialize properties starting with $', function() {
|
||||
expect(toJson({$few: 'v', $$some:'value'}, false)).toEqual('{}');
|
||||
});
|
||||
|
||||
|
||||
it('should not serialize undefined values', function() {
|
||||
expect(angular.toJson({A:undefined})).toEqual('{}');
|
||||
});
|
||||
|
||||
|
||||
it('should not serialize $window object', function() {
|
||||
expect(toJson(window)).toEqual('"$WINDOW"');
|
||||
});
|
||||
|
||||
|
||||
it('should not serialize $document object', function() {
|
||||
expect(toJson(document)).toEqual('"$DOCUMENT"');
|
||||
});
|
||||
|
||||
|
||||
it('should not serialize scope instances', inject(function($rootScope) {
|
||||
expect(toJson({key: $rootScope})).toEqual('{"key":"$SCOPE"}');
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue