47 行
1.8 KiB
JavaScript
47 行
1.8 KiB
JavaScript
|
|
'use strict'
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 安全协议唯一使用的 canonical JSON。对象键按字典序排列,null/undefined 字段省略;
|
||
|
|
* 非 JSON 协议值、数组空位和不确定的对象形态直接拒绝。
|
||
|
|
*/
|
||
|
|
function canonicalJson(value) {
|
||
|
|
if (value === null) return 'null'
|
||
|
|
if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value)
|
||
|
|
if (typeof value === 'number') {
|
||
|
|
if (!Number.isFinite(value)) throw new TypeError('Canonical JSON requires finite numbers')
|
||
|
|
return JSON.stringify(value)
|
||
|
|
}
|
||
|
|
if (Array.isArray(value)) {
|
||
|
|
for (let index = 0; index < value.length; index += 1) {
|
||
|
|
if (!Object.prototype.hasOwnProperty.call(value, index) || value[index] === undefined) {
|
||
|
|
throw new TypeError('Canonical JSON arrays cannot contain holes or undefined')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return `[${value.map(canonicalJson).join(',')}]`
|
||
|
|
}
|
||
|
|
if (value && typeof value === 'object') {
|
||
|
|
const prototype = Object.getPrototypeOf(value)
|
||
|
|
if (prototype !== Object.prototype && prototype !== null) {
|
||
|
|
throw new TypeError('Canonical JSON only supports plain objects')
|
||
|
|
}
|
||
|
|
const keys = Reflect.ownKeys(value)
|
||
|
|
if (keys.some(key => typeof key === 'symbol')) {
|
||
|
|
throw new TypeError('Canonical JSON does not support symbol keys')
|
||
|
|
}
|
||
|
|
for (const key of keys) {
|
||
|
|
const descriptor = Object.getOwnPropertyDescriptor(value, key)
|
||
|
|
if (!descriptor?.enumerable || !('value' in descriptor)) {
|
||
|
|
throw new TypeError('Canonical JSON only supports enumerable data properties')
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return `{${keys
|
||
|
|
.filter(key => value[key] !== null && value[key] !== undefined)
|
||
|
|
.sort()
|
||
|
|
.map(key => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
||
|
|
.join(',')}}`
|
||
|
|
}
|
||
|
|
throw new TypeError(`Canonical JSON does not support ${typeof value}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = { canonicalJson }
|