json-stringify-deterministic
Version:
deterministic version of JSON.stringify() so you can get a consistent hash from stringified results.
88 lines (70 loc) • 2.63 kB
JavaScript
'use strict'
const DEFAULTS = require('./defaults')
const isFunction = require('./util').isFunction
const isBoolean = require('./util').isBoolean
const isObject = require('./util').isObject
const isArray = require('./util').isArray
const isRegex = require('./util').isRegex
const assign = require('./util').assign
const keys = require('./util').keys
function serialize (obj) {
if (obj === null || obj === undefined) return obj
if (isRegex(obj)) return obj.toString()
return obj.toJSON ? obj.toJSON() : obj
}
function stringifyDeterministic (obj, opts) {
opts = opts || assign({}, DEFAULTS)
if (isFunction(opts)) opts = { compare: opts }
const space = opts.space || DEFAULTS.space
const cycles = isBoolean(opts.cycles) ? opts.cycles : DEFAULTS.cycles
const replacer = opts.replacer || DEFAULTS.replacer
const stringify = opts.stringify || DEFAULTS.stringify
const compare = opts.compare && (function (f) {
return function (node) {
return function (a, b) {
const aobj = { key: a, value: node[a] }
const bobj = { key: b, value: node[b] }
return f(aobj, bobj)
}
}
})(opts.compare)
// Detect circular structure in obj and raise error efficiently.
if (!cycles) stringify(obj)
const seen = []
return (function _deterministic (parent, key, node, level) {
const indent = space ? ('\n' + new Array(level + 1).join(space)) : ''
const colonSeparator = space ? ': ' : ':'
node = serialize(node)
node = replacer.call(parent, key, node)
if (node === undefined) return
if (!isObject(node) || node === null) return stringify(node)
if (isArray(node)) {
const out = []
for (let i = 0; i < node.length; i++) {
const item = _deterministic(node, i, node[i], level + 1) || stringify(null)
out.push(indent + space + item)
}
return '[' + out.join(',') + indent + ']'
} else {
if (cycles) {
if (seen.indexOf(node) !== -1) {
return stringify('[Circular]')
} else {
seen.push(node)
}
}
const nodeKeys = keys(node).sort(compare && compare(node))
const out = []
for (let i = 0; i < nodeKeys.length; i++) {
const key = nodeKeys[i]
const value = _deterministic(node, key, node[key], level + 1)
if (!value) continue
const keyValue = stringify(key) + colonSeparator + value
out.push(indent + space + keyValue)
}
seen.splice(seen.indexOf(node), 1)
return '{' + out.join(',') + indent + '}'
}
})({ '': obj }, '', obj, 0)
}
module.exports = stringifyDeterministic