json-canonicalize
Version:
JSON canonicalize function
110 lines (99 loc) • 3.22 kB
text/typescript
export interface IOptions {
exclude?: string | string[]
include?: string[]
allowCircular?: boolean
}
export function canonicalizeEx(obj: any, options?: IOptions) {
let buffer = ''
const vInclude = options && options.include
let vExclude = options && options.exclude
if (vExclude) {
if (typeof vExclude === 'string') vExclude = [vExclude]
}
if (vInclude) vInclude.sort()
const visited = new WeakMap<object, boolean>()
const allowCircular = options && options?.allowCircular
serialize(obj)
return buffer
function serialize(object: any, parent?: object) {
if (
object === null ||
typeof object !== 'object' ||
object.toJSON != null
) {
/////////////////////////////////////////////////
// Primitive data type - Use ES6/JSON //
/////////////////////////////////////////////////
buffer += JSON.stringify(object)
// } else if (object instanceof Date) {
// buffer += JSON.stringify(object);
} else if (Array.isArray(object)) {
/////////////////////////////////////////////////
// Array - Maintain element order //
/////////////////////////////////////////////////
if (visited.has(object)) {
if (!allowCircular) {
throw new Error('Circular reference detected')
}
buffer += '"[Circular]"'
return
}
visited.set(object, true)
buffer += '['
let next = false
object.forEach((element) => {
if (next) {
buffer += ','
}
next = true
/////////////////////////////////////////
// Array element - Recursive expansion //
/////////////////////////////////////////
serialize(element, object)
})
buffer += ']'
} else {
/////////////////////////////////////////////////
// Object - Sort properties before serializing //
/////////////////////////////////////////////////
if (visited.has(object)) {
if (!allowCircular) {
throw new Error('Circular reference detected')
}
buffer += '"[Circular]"'
return
}
visited.set(object, true)
buffer += '{'
if (!parent && vInclude) {
vInclude.forEach((property, index) => {
if (!object.hasOwnProperty(property)) return
addProp(object, property, index)
})
} else {
const vKeys = Object.keys(object).sort()
vKeys.forEach((property, index) => addProp(object, property, index))
}
buffer += '}'
}
}
function addProp(object: any, property: string, index: number) {
if (vExclude && vExclude.length) {
for (const v of vExclude) {
if (v === property) return
}
}
if (index > 0) {
buffer += ','
}
///////////////////////////////////////////////
// Property names are strings - Use ES6/JSON //
///////////////////////////////////////////////
buffer += JSON.stringify(property)
buffer += ':'
//////////////////////////////////////////
// Property value - Recursive expansion //
//////////////////////////////////////////
serialize(object[property], object)
}
}