UNPKG

4.27 kBJavaScriptView Raw
1'use strict';
2
3/** @type {typeof JSON.stringify} */
4var jsonStringify = (typeof JSON !== 'undefined' ? JSON : require('jsonify')).stringify;
5
6var isArray = require('isarray');
7var objectKeys = require('object-keys');
8var callBind = require('call-bind');
9var callBound = require('call-bound');
10
11var $join = callBound('Array.prototype.join');
12var $indexOf = callBound('Array.prototype.indexOf');
13var $splice = callBound('Array.prototype.splice');
14var $sort = callBound('Array.prototype.sort');
15
16/** @type {(n: number, char: string) => string} */
17var strRepeat = function repeat(n, char) {
18 var str = '';
19 for (var i = 0; i < n; i += 1) {
20 str += char;
21 }
22 return str;
23};
24
25/** @type {(parent: import('.').Node, key: import('.').Key, value: unknown) => unknown} */
26var defaultReplacer = function (_parent, _key, value) { return value; };
27
28/** @type {import('.')} */
29module.exports = function stableStringify(obj) {
30 /** @type {Parameters<import('.')>[1]} */
31 var opts = arguments.length > 1 ? arguments[1] : void undefined;
32 var space = (opts && opts.space) || '';
33 if (typeof space === 'number') { space = strRepeat(space, ' '); }
34 var cycles = !!opts && typeof opts.cycles === 'boolean' && opts.cycles;
35 /** @type {undefined | typeof defaultReplacer} */
36 var replacer = opts && opts.replacer ? callBind(opts.replacer) : defaultReplacer;
37
38 var cmpOpt = typeof opts === 'function' ? opts : opts && opts.cmp;
39 /** @type {undefined | (<T extends import('.').NonArrayNode>(node: T) => (a: Exclude<keyof T, symbol | number>, b: Exclude<keyof T, symbol | number>) => number)} */
40 var cmp = cmpOpt && function (node) {
41 // eslint-disable-next-line no-extra-parens
42 var get = /** @type {NonNullable<typeof cmpOpt>} */ (cmpOpt).length > 2
43 && /** @type {import('.').Getter['get']} */ function get(k) { return node[k]; };
44 return function (a, b) {
45 // eslint-disable-next-line no-extra-parens
46 return /** @type {NonNullable<typeof cmpOpt>} */ (cmpOpt)(
47 { key: a, value: node[a] },
48 { key: b, value: node[b] },
49 // @ts-expect-error TS doesn't understand the optimization used here
50 get ? /** @type {import('.').Getter} */ { __proto__: null, get: get } : void undefined
51 );
52 };
53 };
54
55 /** @type {import('.').Node[]} */
56 var seen = [];
57 return (/** @type {(parent: import('.').Node, key: string | number, node: unknown, level: number) => string | undefined} */
58 function stringify(parent, key, node, level) {
59 var indent = space ? '\n' + strRepeat(level, space) : '';
60 var colonSeparator = space ? ': ' : ':';
61
62 // eslint-disable-next-line no-extra-parens
63 if (node && /** @type {{ toJSON?: unknown }} */ (node).toJSON && typeof /** @type {{ toJSON?: unknown }} */ (node).toJSON === 'function') {
64 // eslint-disable-next-line no-extra-parens
65 node = /** @type {{ toJSON: Function }} */ (node).toJSON();
66 }
67
68 node = replacer(parent, key, node);
69
70 if (node === undefined) {
71 return;
72 }
73 if (typeof node !== 'object' || node === null) {
74 return jsonStringify(node);
75 }
76 if (isArray(node)) {
77 var out = [];
78 for (var i = 0; i < node.length; i++) {
79 var item = stringify(node, i, node[i], level + 1) || jsonStringify(null);
80 out[out.length] = indent + space + item;
81 }
82 return '[' + $join(out, ',') + indent + ']';
83 }
84
85 if ($indexOf(seen, node) !== -1) {
86 if (cycles) { return jsonStringify('__cycle__'); }
87 throw new TypeError('Converting circular structure to JSON');
88 } else {
89 seen[seen.length] = /** @type {import('.').NonArrayNode} */ (node);
90 }
91
92 /** @type {import('.').Key[]} */
93 // eslint-disable-next-line no-extra-parens
94 var keys = $sort(objectKeys(node), cmp && cmp(/** @type {import('.').NonArrayNode} */ (node)));
95 var out = [];
96 for (var i = 0; i < keys.length; i++) {
97 var key = keys[i];
98 // eslint-disable-next-line no-extra-parens
99 var value = stringify(/** @type {import('.').Node} */ (node), key, /** @type {import('.').NonArrayNode} */ (node)[key], level + 1);
100
101 if (!value) { continue; }
102
103 var keyValue = jsonStringify(key)
104 + colonSeparator
105 + value;
106
107 out[out.length] = indent + space + keyValue;
108 }
109 $splice(seen, $indexOf(seen, node), 1);
110 return '{' + $join(out, ',') + indent + '}';
111 }({ '': obj }, '', obj, 0)
112 );
113};
114
\No newline at end of file