-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstringify.ts
More file actions
113 lines (110 loc) · 3.28 KB
/
Copy pathstringify.ts
File metadata and controls
113 lines (110 loc) · 3.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import {
CustomStringifyOptions,
DefaultedStringifyOptions,
StringifyOptions,
StringifyType,
TypedValue,
} from './types.js';
const convertType = <T extends string>(
obj: unknown,
options: DefaultedStringifyOptions<T>,
): TypedValue<StringifyType> | undefined => {
const { bigintRadix, dateFormat, ignoreFunctions, skipNull, skipUndefined } = options;
if (obj === null) {
return skipNull ? undefined : { t: 'null' };
}
if (obj === undefined) {
return skipUndefined ? undefined : { t: 'undefined' };
}
if (obj instanceof Date) {
return { t: 'Date', v: dateFormat === 'iso' ? obj.toISOString() : obj.getTime().toString() };
}
if (obj instanceof Error) {
return {
t: 'Error',
v: stringify({ name: obj.name, message: obj.message, stack: obj.stack }, options),
};
}
if (obj instanceof Map) {
return { t: 'Map', v: stringify([...obj], options) };
}
if (obj instanceof Set) {
return { t: 'Set', v: stringify([...obj], options) };
}
switch (typeof obj) {
case 'bigint': {
if (!Number.isSafeInteger(bigintRadix) || bigintRadix < 2 || bigintRadix > 36) {
throw new RangeError('bigintRadix must be an integer between 2 and 36');
}
if (bigintRadix === 10) {
return { t: 'bigint', v: obj.toString() };
}
return { t: 'bigint', v: 'r' + bigintRadix.toString(36).slice(-1) + obj.toString(bigintRadix) };
}
case 'boolean': {
return { t: 'boolean', v: obj ? '1' : '0' };
}
case 'function': {
if (!ignoreFunctions) {
throw new Error('Functions can not be stringified');
}
return { t: 'function' };
}
case 'number': {
return { t: 'number', v: obj.toString() };
}
case 'string': {
return { t: 'string', v: obj };
}
case 'symbol': {
return { t: 'symbol', v: Symbol.keyFor(obj) };
}
}
throw new Error(`Unknown datatype: ${typeof obj}`);
};
const decent = <T extends string>(obj: unknown, options: DefaultedStringifyOptions<T>): unknown => {
const { customStringify, currentDepth, maxDepth } = options;
if (currentDepth > maxDepth) {
throw new Error('Max depth exceeded');
}
if (customStringify) {
const { useResult, result } = customStringify(obj, {
...options,
currentDepth: currentDepth + 1,
} as CustomStringifyOptions<T>);
if (useResult) {
return result;
}
}
if (Array.isArray(obj)) {
return obj.map((obj) => decent(obj, { ...options, currentDepth: currentDepth + 1 }));
}
if (
obj &&
typeof obj === 'object' &&
!(obj instanceof Date) &&
!(obj instanceof Error) &&
!(obj instanceof Map) &&
!(obj instanceof Set)
) {
const tmpObj: { [key: string]: unknown } = {};
for (const [key, value] of Object.entries(obj)) {
tmpObj[key] = decent(value, { ...options, currentDepth: currentDepth + 1 });
}
return tmpObj;
}
return convertType(obj, { ...options, currentDepth: currentDepth + 1 });
};
export const stringify = <T extends string = StringifyType>(obj: unknown, options: StringifyOptions<T> = {}): string =>
JSON.stringify(
decent(obj, {
bigintRadix: options.bigintRadix ?? 10,
currentDepth: options.currentDepth ?? 0,
customStringify: options.customStringify,
dateFormat: options.dateFormat ?? 'iso',
ignoreFunctions: options.ignoreFunctions ?? false,
maxDepth: options.maxDepth ?? 20,
skipNull: options.skipNull ?? false,
skipUndefined: options.skipUndefined ?? false,
}),
);