forked from Liquifact/Liquifact-frontend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsafeJson.js
More file actions
128 lines (110 loc) · 3.63 KB
/
Copy pathsafeJson.js
File metadata and controls
128 lines (110 loc) · 3.63 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* @file Safe JSON formatting utilities for rendering untrusted API responses.
*
* These helpers bound the size and shape of arbitrary JSON before it reaches
* the DOM, preventing DoS via giant payloads or deeply nested objects.
*
* @module safeJson
*/
const DEFAULT_MAX_LENGTH = 2000;
const DEFAULT_MAX_DEPTH = 5;
const TRUNCATION_MARKER = "…(truncated)";
/**
* Truncates a string to `maxLength` characters, appending a truncation marker
* when the string is longer than the limit.
*
* @param {unknown} value - Value to coerce to string and truncate.
* @param {number} [maxLength=2000] - Maximum allowed character count.
* @returns {string} Truncated string with marker if applicable.
*/
function truncateString(value, maxLength = DEFAULT_MAX_LENGTH) {
const str = String(value ?? "");
if (str.length <= maxLength) {
return str;
}
return str.slice(0, maxLength) + TRUNCATION_MARKER;
}
/**
* Recursively limits the depth of an object. Any value at a depth greater
* than `maxDepth` is replaced with a placeholder string.
*
* @param {unknown} obj - Value to depth-limit.
* @param {number} [maxDepth=5] - Maximum nesting depth.
* @param {number} [depth=0] - Internal recursion depth counter.
* @param {WeakSet} [seen] - Internal set for circular reference detection.
* @returns {unknown} A new value with deep nesting replaced.
*/
function limitDepth(obj, maxDepth = DEFAULT_MAX_DEPTH, depth = 0, seen) {
if (obj === null || obj === undefined) {
return obj;
}
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
return obj;
}
if (depth > maxDepth) {
return "[Depth limit reached]";
}
if (typeof obj === "object") {
const seenSet = seen ?? new WeakSet();
if (seenSet.has(obj)) {
return "[Circular]";
}
seenSet.add(obj);
if (Array.isArray(obj)) {
return obj.map((item) => limitDepth(item, maxDepth, depth + 1, seenSet));
}
const result = {};
for (const key of Object.keys(obj)) {
result[key] = limitDepth(obj[key], maxDepth, depth + 1, seenSet);
}
return result;
}
return obj;
}
/**
* Extracts only the specified known fields from an object, ignoring keys
* that are not present.
*
* @param {unknown} obj - Source object.
* @param {string[]} [fields] - Keys to extract. Defaults to
* `['status', 'message', 'version']`.
* @returns {Record<string, unknown>} Plain object containing only existent fields.
*/
function extractKnownFields(obj, fields = ["status", "message", "version"]) {
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
return {};
}
const result = {};
for (const key of fields) {
if (key in obj) {
result[key] = obj[key];
}
}
return result;
}
/**
* Safely stringifies a value for display by first limiting its object depth,
* then stringifying, then truncating the resulting string.
*
* @param {unknown} obj - Value to stringify.
* @param {object} [options]
* @param {number} [options.maxLength=2000] - Max characters for the output.
* @param {number} [options.maxDepth=5] - Max object nesting depth.
* @returns {string} Safe, truncated JSON string.
*/
function safeJsonStringify(
obj,
{ maxLength = DEFAULT_MAX_LENGTH, maxDepth = DEFAULT_MAX_DEPTH } = {}
) {
if (obj === undefined || obj === null) {
return String(obj);
}
try {
const depthLimited = limitDepth(obj, maxDepth);
const json = JSON.stringify(depthLimited, null, 2);
return truncateString(json, maxLength);
} catch {
return String(obj);
}
}
export { truncateString, limitDepth, extractKnownFields, safeJsonStringify };