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.
162 lines
4.3 KiB
162 lines
4.3 KiB
4 years ago
|
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)
|
||
|
}
|
||
|
}
|