source: imaps-frontend/node_modules/@discoveryjs/json-ext/src/stringify-info.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 4 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 7.0 KB
Line 
1import { normalizeStringifyOptions, replaceValue } from './utils.js';
2
3const hasOwn = typeof Object.hasOwn === 'function'
4 ? Object.hasOwn
5 : (object, key) => Object.hasOwnProperty.call(object, key);
6
7// https://tc39.es/ecma262/#table-json-single-character-escapes
8const escapableCharCodeSubstitution = { // JSON Single Character Escape Sequences
9 0x08: '\\b',
10 0x09: '\\t',
11 0x0a: '\\n',
12 0x0c: '\\f',
13 0x0d: '\\r',
14 0x22: '\\\"',
15 0x5c: '\\\\'
16};
17
18const charLength2048 = Uint8Array.from({ length: 2048 }, (_, code) => {
19 if (hasOwn(escapableCharCodeSubstitution, code)) {
20 return 2; // \X
21 }
22
23 if (code < 0x20) {
24 return 6; // \uXXXX
25 }
26
27 return code < 128 ? 1 : 2; // UTF8 bytes
28});
29
30function isLeadingSurrogate(code) {
31 return code >= 0xD800 && code <= 0xDBFF;
32}
33
34function isTrailingSurrogate(code) {
35 return code >= 0xDC00 && code <= 0xDFFF;
36}
37
38function stringLength(str) {
39 // Fast path to compute length when a string contains only characters encoded as single bytes
40 if (!/[^\x20\x21\x23-\x5B\x5D-\x7F]/.test(str)) {
41 return str.length + 2;
42 }
43
44 let len = 0;
45 let prevLeadingSurrogate = false;
46
47 for (let i = 0; i < str.length; i++) {
48 const code = str.charCodeAt(i);
49
50 if (code < 2048) {
51 len += charLength2048[code];
52 } else if (isLeadingSurrogate(code)) {
53 len += 6; // \uXXXX since no pair with trailing surrogate yet
54 prevLeadingSurrogate = true;
55 continue;
56 } else if (isTrailingSurrogate(code)) {
57 len = prevLeadingSurrogate
58 ? len - 2 // surrogate pair (4 bytes), since we calculate prev leading surrogate as 6 bytes, substruct 2 bytes
59 : len + 6; // \uXXXX
60 } else {
61 len += 3; // code >= 2048 is 3 bytes length for UTF8
62 }
63
64 prevLeadingSurrogate = false;
65 }
66
67 return len + 2; // +2 for quotes
68}
69
70// avoid producing a string from a number
71function intLength(num) {
72 let len = 0;
73
74 if (num < 0) {
75 len = 1;
76 num = -num;
77 }
78
79 if (num >= 1e9) {
80 len += 9;
81 num = (num - num % 1e9) / 1e9;
82 }
83
84 if (num >= 1e4) {
85 if (num >= 1e6) {
86 return len + (num >= 1e8
87 ? 9
88 : num >= 1e7 ? 8 : 7
89 );
90 }
91 return len + (num >= 1e5 ? 6 : 5);
92 }
93
94 return len + (num >= 1e2
95 ? num >= 1e3 ? 4 : 3
96 : num >= 10 ? 2 : 1
97 );
98};
99
100function primitiveLength(value) {
101 switch (typeof value) {
102 case 'string':
103 return stringLength(value);
104
105 case 'number':
106 return Number.isFinite(value)
107 ? Number.isInteger(value)
108 ? intLength(value)
109 : String(value).length
110 : 4 /* null */;
111
112 case 'boolean':
113 return value ? 4 /* true */ : 5 /* false */;
114
115 case 'undefined':
116 case 'object':
117 return 4; /* null */
118
119 default:
120 return 0;
121 }
122}
123
124export function stringifyInfo(value, ...args) {
125 const { replacer, getKeys, ...options } = normalizeStringifyOptions(...args);
126 const continueOnCircular = Boolean(options.continueOnCircular);
127 const space = options.space?.length || 0;
128
129 const keysLength = new Map();
130 const visited = new Map();
131 const circular = new Set();
132 const stack = [];
133 const root = { '': value };
134 let stop = false;
135 let bytes = 0;
136 let spaceBytes = 0;
137 let objects = 0;
138
139 walk(root, '', value);
140
141 // when value is undefined or replaced for undefined
142 if (bytes === 0) {
143 bytes += 9; // FIXME: that's the length of undefined, should we normalize behaviour to convert it to null?
144 }
145
146 return {
147 bytes: isNaN(bytes) ? Infinity : bytes + spaceBytes,
148 spaceBytes: space > 0 && isNaN(bytes) ? Infinity : spaceBytes,
149 circular: [...circular]
150 };
151
152 function walk(holder, key, value) {
153 if (stop) {
154 return;
155 }
156
157 value = replaceValue(holder, key, value, replacer);
158
159 if (value === null || typeof value !== 'object') {
160 // primitive
161 if (value !== undefined || Array.isArray(holder)) {
162 bytes += primitiveLength(value);
163 }
164 } else {
165 // check for circular references
166 if (stack.includes(value)) {
167 circular.add(value);
168 bytes += 4; // treat as null
169
170 if (!continueOnCircular) {
171 stop = true;
172 }
173
174 return;
175 }
176
177 // Using 'visited' allows avoiding hang-ups in cases of highly interconnected object graphs;
178 // for example, a list of git commits with references to parents can lead to N^2 complexity for traversal,
179 // and N when 'visited' is used
180 if (visited.has(value)) {
181 bytes += visited.get(value);
182
183 return;
184 }
185
186 objects++;
187
188 const prevObjects = objects;
189 const valueBytes = bytes;
190 let valueLength = 0;
191
192 stack.push(value);
193
194 if (Array.isArray(value)) {
195 // array
196 valueLength = value.length;
197
198 for (let i = 0; i < valueLength; i++) {
199 walk(value, i, value[i]);
200 }
201 } else {
202 // object
203 let prevLength = bytes;
204
205 for (const key of getKeys(value)) {
206 walk(value, key, value[key]);
207
208 if (prevLength !== bytes) {
209 let keyLen = keysLength.get(key);
210
211 if (keyLen === undefined) {
212 keysLength.set(key, keyLen = stringLength(key) + 1); // "key":
213 }
214
215 // value is printed
216 bytes += keyLen;
217 valueLength++;
218 prevLength = bytes;
219 }
220 }
221 }
222
223 bytes += valueLength === 0
224 ? 2 // {} or []
225 : 1 + valueLength; // {} or [] + commas
226
227 if (space > 0 && valueLength > 0) {
228 spaceBytes +=
229 // a space between ":" and a value for each object entry
230 (Array.isArray(value) ? 0 : valueLength) +
231 // the formula results from folding the following components:
232 // - for each key-value or element: ident + newline
233 // (1 + stack.length * space) * valueLength
234 // - ident (one space less) before "}" or "]" + newline
235 // (stack.length - 1) * space + 1
236 (1 + stack.length * space) * (valueLength + 1) - space;
237 }
238
239 stack.pop();
240
241 // add to 'visited' only objects that contain nested objects
242 if (prevObjects !== objects) {
243 visited.set(value, bytes - valueBytes);
244 }
245 }
246 }
247};
Note: See TracBrowser for help on using the repository browser.