diff --git a/index.js b/index.js index 4bbf9aa..ab61331 100644 --- a/index.js +++ b/index.js @@ -12,17 +12,96 @@ module.exports = { } }, - sortAndFilterJson({ fields, entity, exclude = [] } = {}) { - const newEntity = {} - for (let key of Object.keys(fields).sort()) { - if (typeof (entity[key] !== 'undefined') && !Number.isNaN(entity[key]) && entity[key] !== Infinity) { - newEntity[key] = entity[key] + // 按顺序展开,哪怕嵌套。 + stringifyOrdered (obj, {cmp, cycles = false, space = '', replacer, schemaColumns, excludeKeys = []} = {}) { + + /* 这个解决方法不考虑缺省值,不能把嵌套对象也按顺序展开。*/ +// return JSON.stringify(obj, Object.keys(schemaColumns || entity).sort().filter(key => ! excludeKeys.includes(key))) // JSON.stringify 可根据第二个数组参数的顺序排序 + + let newObj = {} + if (schemaColumns) { + for (let key in schemaColumns){ + if (schemaColumns.hasOwnProperty(key) && ! schemaColumns[key].hashExclusive && ! excludeKeys.includes(key)) { + // JSON.stringify (包括本函数)会把 NaN 或 Infinity 输出为 null,会把 undefined 忽略掉。 + // 而在typeorm sqlite数据库中,undefined 会自动存为 schemaColumns[key].default 或 null。从数据库读出时,就会和事先JSON.stringify的结果不一致。 + // 为了和数据库保持一致习惯,对schemaColumns里的键值最好把 undefined 也设为 default 或 null。 + if (obj[key] === undefined || Number.isNaN(obj[key]) || obj[key] === Infinity) { + newObj[key] = schemaColumns[key].default || null + obj[key] = schemaColumns[key].default || null // 确保内存中的数据和数据库保持一致 + }else { + newObj[key] = obj[key] + } + } } + }else{ + newObj = obj } - for (let exkey of exclude) { - delete newEntity[exkey] - } - return JSON.stringify(newEntity) + + /* 以下代码来自 https://github.com/substack/json-stable-stringify 可把嵌套的复杂值也按顺序输出,例如 { c: 8, b: [{z:6,y:5,x:4},7], a: 3 } */ + if (typeof space === 'number') space = Array(space+1).join(' ') + var cycles = (typeof cycles === 'boolean') ? cycles : false + var replacer = replacer || function(key, value) { return value } + + var cmp = cmp && (function (f) { + return function (node) { + return function (a, b) { + var aobj = { key: a, value: node[a] } + var bobj = { key: b, value: node[b] } + return f(aobj, bobj) + } + } + })(cmp) + + var seen = [] + return (function stringify (parent, key, node, level) { + var indent = space ? ('\n' + new Array(level + 1).join(space)) : '' + var colonSeparator = space ? ': ' : ':' + + if (node && node.toJSON && typeof node.toJSON === 'function') { + node = node.toJSON() + } + + node = replacer.call(parent, key, node) + + if (node === undefined) { + return + } + if (typeof node !== 'object' || node === null) { + return JSON.stringify(node) + } + if (Array.isArray(node)) { + var out = [] + for (var i = 0; i < node.length; i++) { + var item = stringify(node, i, node[i], level+1) || JSON.stringify(null) + out.push(indent + space + item) + } + return '[' + out.join(',') + indent + ']' + } + else { + if (seen.indexOf(node) !== -1) { + if (cycles) return JSON.stringify('__cycle__') + throw new TypeError('Converting circular structure to JSON') + } + else seen.push(node) + + var keys = Object.keys(node).sort(cmp && cmp(node)) + var out = [] + for (var i = 0; i < keys.length; i++) { + var key = keys[i] + var value = stringify(node, key, node[key], level+1) + + if(!value) continue + + var keyValue = JSON.stringify(key) + + colonSeparator + + value + + out.push(indent + space + keyValue) + } + seen.splice(seen.indexOf(node), 1) + return '{' + out.join(',') + indent + '}' + } + })({ '': newObj }, '', newObj, 0) }, name2port(name='') {