module.exports = stringify stringify.default = stringify stringify.stable = deterministicStringify stringify.stableStringify = deterministicStringify var arr = [] var replacerStack = [] // Regular stringify function stringify (obj, replacer, spacer) { decirc(obj, '', [], undefined) var res if (replacerStack.length === 0) { res = JSON.stringify(obj, replacer, spacer) } else { res = JSON.stringify(obj, replaceGetterValues(replacer), spacer) } 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 decirc (val, k, stack, parent) { var i if (typeof val === 'object' && val !== null) { for (i = 0; i < stack.length; i++) { if (stack[i] === val) { var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k) if (propertyDescriptor.get !== undefined) { if (propertyDescriptor.configurable) { Object.defineProperty(parent, k, { value: '[Circular]' }) arr.push([parent, k, val, propertyDescriptor]) } else { replacerStack.push([val, k]) } } else { parent[k] = '[Circular]' arr.push([parent, k, val]) } 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, stack, val) } } else { var keys = Object.keys(val) for (i = 0; i < keys.length; i++) { var key = keys[i] decirc(val[key], key, stack, val) } } 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) { var tmp = deterministicDecirc(obj, '', [], undefined) || obj var res if (replacerStack.length === 0) { res = JSON.stringify(tmp, replacer, spacer) } else { res = JSON.stringify(tmp, replaceGetterValues(replacer), spacer) } 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, stack, parent) { var i if (typeof val === 'object' && val !== null) { for (i = 0; i < stack.length; i++) { if (stack[i] === val) { var propertyDescriptor = Object.getOwnPropertyDescriptor(parent, k) if (propertyDescriptor.get !== undefined) { if (propertyDescriptor.configurable) { Object.defineProperty(parent, k, { value: '[Circular]' }) arr.push([parent, k, val, propertyDescriptor]) } else { replacerStack.push([val, k]) } } else { parent[k] = '[Circular]' arr.push([parent, k, val]) } return } } if (typeof val.toJSON === 'function') { 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, stack, val) } } 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, stack, val) tmp[key] = val[key] } if (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 [Circular] function replaceGetterValues (replacer) { replacer = 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 = '[Circular]' replacerStack.splice(i, 1) break } } } return replacer.call(this, key, val) } }