-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
122 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/* | ||
Copyright (c) 2014, Yahoo! Inc. All rights reserved. | ||
Copyrights licensed under the New BSD License. | ||
See the accompanying LICENSE file for terms. | ||
*/ | ||
|
||
'use strict'; | ||
|
||
// Generate an internal UID to make the regexp pattern harder to guess. | ||
var UID = Math.floor(Math.random() * 0x10000000000).toString(16); | ||
var PLACE_HOLDER_REGEXP = new RegExp('"@__(F|R|D)-' + UID + '-(\\d+)__@"', 'g'); | ||
|
||
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g; | ||
var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g; | ||
|
||
// Mapping of unsafe HTML and invalid JavaScript line terminator chars to their | ||
// Unicode char counterparts which are safe to use in JavaScript strings. | ||
var ESCAPED_CHARS = { | ||
'<' : '\\u003C', | ||
'>' : '\\u003E', | ||
'/' : '\\u002F', | ||
'\u2028': '\\u2028', | ||
'\u2029': '\\u2029' | ||
}; | ||
|
||
function escapeUnsafeChars(unsafeChar) { | ||
return ESCAPED_CHARS[unsafeChar]; | ||
} | ||
|
||
module.exports = function serialize(obj, options) { | ||
options || (options = {}); | ||
|
||
// Backwards-compatibility for `space` as the second argument. | ||
if (typeof options === 'number' || typeof options === 'string') { | ||
options = {space: options}; | ||
} | ||
|
||
var functions = []; | ||
var regexps = []; | ||
var dates = []; | ||
|
||
// Returns placeholders for functions and regexps (identified by index) | ||
// which are later replaced by their string representation. | ||
function replacer(key, value) { | ||
if (!value) { | ||
return value; | ||
} | ||
|
||
// If the value is an object w/ a toJSON method, toJSON is called before | ||
// the replacer runs, so we use this[key] to get the non-toJSONed value. | ||
var origValue = this[key]; | ||
var type = typeof origValue; | ||
|
||
if (type === 'object') { | ||
if(origValue instanceof RegExp) { | ||
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@'; | ||
} | ||
|
||
if(origValue instanceof Date) { | ||
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@'; | ||
} | ||
} | ||
|
||
if (type === 'function') { | ||
return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@'; | ||
} | ||
|
||
return value; | ||
} | ||
|
||
var str; | ||
|
||
// Creates a JSON string representation of the value. | ||
// NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args. | ||
if (options.isJSON && !options.space) { | ||
str = JSON.stringify(obj); | ||
} else { | ||
str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space); | ||
} | ||
|
||
// Protects against `JSON.stringify()` returning `undefined`, by serializing | ||
// to the literal string: "undefined". | ||
if (typeof str !== 'string') { | ||
return String(str); | ||
} | ||
|
||
// Replace unsafe HTML and invalid JavaScript line terminator chars with | ||
// their safe Unicode char counterpart. This _must_ happen before the | ||
// regexps and functions are serialized and added back to the string. | ||
if (options.unsafe !== true) { | ||
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars); | ||
} | ||
|
||
if (functions.length === 0 && regexps.length === 0 && dates.length === 0) { | ||
return str; | ||
} | ||
|
||
// Replaces all occurrences of function, regexp and date placeholders in the | ||
// JSON string with their string representations. If the original value can | ||
// not be found, then `undefined` is used. | ||
return str.replace(PLACE_HOLDER_REGEXP, function (match, type, valueIndex) { | ||
if (type === 'D') { | ||
return "new Date(\"" + dates[valueIndex].toISOString() + "\")"; | ||
} | ||
|
||
if (type === 'R') { | ||
return regexps[valueIndex].toString(); | ||
} | ||
|
||
var fn = functions[valueIndex]; | ||
var serializedFn = fn.toString(); | ||
|
||
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) { | ||
throw new TypeError('Serializing native function: ' + fn.name); | ||
} | ||
|
||
return serializedFn; | ||
}); | ||
} |