You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					230 lines
				
				5.7 KiB
			
		
		
			
		
	
	
					230 lines
				
				5.7 KiB
			| 
											3 years ago
										 | module.exports = stringify | ||
|  | stringify.default = stringify | ||
|  | stringify.stable = deterministicStringify | ||
|  | stringify.stableStringify = deterministicStringify | ||
|  | 
 | ||
|  | var LIMIT_REPLACE_NODE = '[...]' | ||
|  | var CIRCULAR_REPLACE_NODE = '[Circular]' | ||
|  | 
 | ||
|  | var arr = [] | ||
|  | var replacerStack = [] | ||
|  | 
 | ||
|  | function defaultOptions () { | ||
|  |   return { | ||
|  |     depthLimit: Number.MAX_SAFE_INTEGER, | ||
|  |     edgesLimit: Number.MAX_SAFE_INTEGER | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | // Regular stringify
 | ||
|  | function stringify (obj, replacer, spacer, options) { | ||
|  |   if (typeof options === 'undefined') { | ||
|  |     options = defaultOptions() | ||
|  |   } | ||
|  | 
 | ||
|  |   decirc(obj, '', 0, [], undefined, 0, options) | ||
|  |   var res | ||
|  |   try { | ||
|  |     if (replacerStack.length === 0) { | ||
|  |       res = JSON.stringify(obj, replacer, spacer) | ||
|  |     } else { | ||
|  |       res = JSON.stringify(obj, replaceGetterValues(replacer), spacer) | ||
|  |     } | ||
|  |   } catch (_) { | ||
|  |     return JSON.stringify('[unable to serialize, circular reference is too complex to analyze]') | ||
|  |   } finally { | ||
|  |     while (arr.length !== 0) { | ||
|  |       var part = arr.pop() | ||
|  |       if (part.length === 4) { | ||
|  |         Object.defineProperty(part[0], part[1], part[3]) | ||
|  |       } else { | ||
|  |         part[0][part[1]] = part[2] | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   return res | ||
|  | } | ||
|  | 
 | ||
|  | function setReplace (replace, val, k, parent) { | ||
|  |   var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k) | ||
|  |   if (propertyDescriptor.get !== undefined) { | ||
|  |     if (propertyDescriptor.configurable) { | ||
|  |       Object.defineProperty(parent, k, { value: replace }) | ||
|  |       arr.push([parent, k, val, propertyDescriptor]) | ||
|  |     } else { | ||
|  |       replacerStack.push([val, k, replace]) | ||
|  |     } | ||
|  |   } else { | ||
|  |     parent[k] = replace | ||
|  |     arr.push([parent, k, val]) | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | function decirc (val, k, edgeIndex, stack, parent, depth, options) { | ||
|  |   depth += 1 | ||
|  |   var i | ||
|  |   if (typeof val === 'object' && val !== null) { | ||
|  |     for (i = 0; i < stack.length; i++) { | ||
|  |       if (stack[i] === val) { | ||
|  |         setReplace(CIRCULAR_REPLACE_NODE, val, k, parent) | ||
|  |         return | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( | ||
|  |       typeof options.depthLimit !== 'undefined' && | ||
|  |       depth > options.depthLimit | ||
|  |     ) { | ||
|  |       setReplace(LIMIT_REPLACE_NODE, val, k, parent) | ||
|  |       return | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( | ||
|  |       typeof options.edgesLimit !== 'undefined' && | ||
|  |       edgeIndex + 1 > options.edgesLimit | ||
|  |     ) { | ||
|  |       setReplace(LIMIT_REPLACE_NODE, val, k, parent) | ||
|  |       return | ||
|  |     } | ||
|  | 
 | ||
|  |     stack.push(val) | ||
|  |     // Optimize for Arrays. Big arrays could kill the performance otherwise!
 | ||
|  |     if (Array.isArray(val)) { | ||
|  |       for (i = 0; i < val.length; i++) { | ||
|  |         decirc(val[i], i, i, stack, val, depth, options) | ||
|  |       } | ||
|  |     } else { | ||
|  |       var keys = Object.keys(val) | ||
|  |       for (i = 0; i < keys.length; i++) { | ||
|  |         var key = keys[i] | ||
|  |         decirc(val[key], key, i, stack, val, depth, options) | ||
|  |       } | ||
|  |     } | ||
|  |     stack.pop() | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | // Stable-stringify
 | ||
|  | function compareFunction (a, b) { | ||
|  |   if (a < b) { | ||
|  |     return -1 | ||
|  |   } | ||
|  |   if (a > b) { | ||
|  |     return 1 | ||
|  |   } | ||
|  |   return 0 | ||
|  | } | ||
|  | 
 | ||
|  | function deterministicStringify (obj, replacer, spacer, options) { | ||
|  |   if (typeof options === 'undefined') { | ||
|  |     options = defaultOptions() | ||
|  |   } | ||
|  | 
 | ||
|  |   var tmp = deterministicDecirc(obj, '', 0, [], undefined, 0, options) || obj | ||
|  |   var res | ||
|  |   try { | ||
|  |     if (replacerStack.length === 0) { | ||
|  |       res = JSON.stringify(tmp, replacer, spacer) | ||
|  |     } else { | ||
|  |       res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer) | ||
|  |     } | ||
|  |   } catch (_) { | ||
|  |     return JSON.stringify('[unable to serialize, circular reference is too complex to analyze]') | ||
|  |   } finally { | ||
|  |     // Ensure that we restore the object as it was.
 | ||
|  |     while (arr.length !== 0) { | ||
|  |       var part = arr.pop() | ||
|  |       if (part.length === 4) { | ||
|  |         Object.defineProperty(part[0], part[1], part[3]) | ||
|  |       } else { | ||
|  |         part[0][part[1]] = part[2] | ||
|  |       } | ||
|  |     } | ||
|  |   } | ||
|  |   return res | ||
|  | } | ||
|  | 
 | ||
|  | function deterministicDecirc (val, k, edgeIndex, stack, parent, depth, options) { | ||
|  |   depth += 1 | ||
|  |   var i | ||
|  |   if (typeof val === 'object' && val !== null) { | ||
|  |     for (i = 0; i < stack.length; i++) { | ||
|  |       if (stack[i] === val) { | ||
|  |         setReplace(CIRCULAR_REPLACE_NODE, val, k, parent) | ||
|  |         return | ||
|  |       } | ||
|  |     } | ||
|  |     try { | ||
|  |       if (typeof val.toJSON === 'function') { | ||
|  |         return | ||
|  |       } | ||
|  |     } catch (_) { | ||
|  |       return | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( | ||
|  |       typeof options.depthLimit !== 'undefined' && | ||
|  |       depth > options.depthLimit | ||
|  |     ) { | ||
|  |       setReplace(LIMIT_REPLACE_NODE, val, k, parent) | ||
|  |       return | ||
|  |     } | ||
|  | 
 | ||
|  |     if ( | ||
|  |       typeof options.edgesLimit !== 'undefined' && | ||
|  |       edgeIndex + 1 > options.edgesLimit | ||
|  |     ) { | ||
|  |       setReplace(LIMIT_REPLACE_NODE, val, k, parent) | ||
|  |       return | ||
|  |     } | ||
|  | 
 | ||
|  |     stack.push(val) | ||
|  |     // Optimize for Arrays. Big arrays could kill the performance otherwise!
 | ||
|  |     if (Array.isArray(val)) { | ||
|  |       for (i = 0; i < val.length; i++) { | ||
|  |         deterministicDecirc(val[i], i, i, stack, val, depth, options) | ||
|  |       } | ||
|  |     } else { | ||
|  |       // Create a temporary object in the required way
 | ||
|  |       var tmp = {} | ||
|  |       var keys = Object.keys(val).sort(compareFunction) | ||
|  |       for (i = 0; i < keys.length; i++) { | ||
|  |         var key = keys[i] | ||
|  |         deterministicDecirc(val[key], key, i, stack, val, depth, options) | ||
|  |         tmp[key] = val[key] | ||
|  |       } | ||
|  |       if (typeof parent !== 'undefined') { | ||
|  |         arr.push([parent, k, val]) | ||
|  |         parent[k] = tmp | ||
|  |       } else { | ||
|  |         return tmp | ||
|  |       } | ||
|  |     } | ||
|  |     stack.pop() | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | // wraps replacer function to handle values we couldn't replace
 | ||
|  | // and mark them as replaced value
 | ||
|  | function replaceGetterValues (replacer) { | ||
|  |   replacer = | ||
|  |     typeof replacer !== 'undefined' | ||
|  |       ? replacer | ||
|  |       : function (k, v) { | ||
|  |         return v | ||
|  |       } | ||
|  |   return function (key, val) { | ||
|  |     if (replacerStack.length > 0) { | ||
|  |       for (var i = 0; i < replacerStack.length; i++) { | ||
|  |         var part = replacerStack[i] | ||
|  |         if (part[1] === key && part[0] === val) { | ||
|  |           val = part[2] | ||
|  |           replacerStack.splice(i, 1) | ||
|  |           break | ||
|  |         } | ||
|  |       } | ||
|  |     } | ||
|  |     return replacer.call(this, key, val) | ||
|  |   } | ||
|  | } |