Skip to content

Commit 8add0fa

Browse files
test(helpers): add JSDoc to decycle
1 parent c627518 commit 8add0fa

1 file changed

Lines changed: 43 additions & 49 deletions

File tree

__tests__/helpers/decycle.ts

Lines changed: 43 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,60 @@ interface DecycledObject {
2828
$ref?: string;
2929
}
3030

31+
/**
32+
* Makes a deep copy of an object or array, assuring that there is at most
33+
* one instance of each object or array in the resulting structure. The
34+
* duplicate references (which might be forming cycles) are replaced with
35+
* an object of the form `{"$ref": PATH}` where the PATH is a JSONPath
36+
* string that locates the first occurance.
37+
*
38+
* @example
39+
* ```ts
40+
* var a = [];
41+
* a[0] = a;
42+
* JSON.stringify(decycle(a)); // '[{"$ref":"$"}]'
43+
* ```
44+
*
45+
* If a replacer function is provided, then it will be called for each value.
46+
* A replacer function receives a value and returns a replacement value.
47+
*
48+
* JSONPath is used to locate the unique object. `$` indicates the top level of
49+
* the object or array. `[NUMBER]` or `[STRING]` indicates a child element or
50+
* property.
51+
*
52+
* @param object - The object or array to decycle.
53+
* @param replacer - Optional replacer function called for each value.
54+
* @returns A deep copy of the object with circular references replaced by `$ref` objects.
55+
*/
56+
export function decycle(object: unknown, replacer?: ReplacerFunction) {
57+
const objects = new WeakMap<object, string>(); // object to path mappings
58+
59+
return deepCopy(object, '$', objects, replacer);
60+
}
61+
62+
/**
63+
* Recursively deep copies a value, replacing circular references with
64+
* `{"$ref": PATH}` objects.
65+
*
66+
* @param value - The current value to copy.
67+
* @param path - The JSONPath to the current value.
68+
* @param objects - WeakMap tracking already-visited objects and their paths.
69+
* @param replacer - Optional replacer function called for each value.
70+
* @returns The deep-copied value.
71+
*/
3172
function deepCopy(
3273
value: unknown,
3374
path: string,
3475
objects: WeakMap<object, string>,
3576
replacer?: ReplacerFunction,
3677
): unknown {
37-
// Recurses through the object, producing the deep copy.
38-
39-
let old_path: string | undefined; // The path of an earlier occurance of value
40-
let nu: unknown[] | DecycledObject; // The new object or array
41-
42-
// If a replacer function was provided, then call it to get a replacement value.
78+
let old_path: string | undefined;
79+
let nu: unknown[] | DecycledObject;
4380

4481
if (replacer !== undefined) {
4582
value = replacer(value);
4683
}
4784

48-
// typeof null === "object", so go on if this value is really an object but not
49-
// one of the weird builtin objects.
50-
5185
if (
5286
typeof value === 'object' &&
5387
value !== null &&
@@ -57,21 +91,13 @@ function deepCopy(
5791
!(value instanceof RegExp) &&
5892
!(value instanceof String)
5993
) {
60-
// If the value is an object or array, look to see if we have already
61-
// encountered it. If so, return a {"$ref":PATH} object. This uses an
62-
// ES6 WeakMap.
63-
6494
old_path = objects.get(value);
6595
if (old_path !== undefined) {
6696
return { $ref: old_path };
6797
}
6898

69-
// Otherwise, accumulate the unique value and its path.
70-
7199
objects.set(value, path);
72100

73-
// If it is an array, replicate the array.
74-
75101
if (Array.isArray(value)) {
76102
nu = [];
77103
(value as unknown[]).forEach(function (element: unknown, i: number) {
@@ -83,8 +109,6 @@ function deepCopy(
83109
);
84110
});
85111
} else {
86-
// If it is an object, replicate the object.
87-
88112
nu = {} as DecycledObject;
89113
Object.keys(value as Record<string, unknown>).forEach(function (
90114
name: string,
@@ -101,33 +125,3 @@ function deepCopy(
101125
}
102126
return value;
103127
}
104-
105-
export function decycle(object: unknown, replacer?: ReplacerFunction) {
106-
// Make a deep copy of an object or array, assuring that there is at most
107-
// one instance of each object or array in the resulting structure. The
108-
// duplicate references (which might be forming cycles) are replaced with
109-
// an object of the form
110-
111-
// {"$ref": PATH}
112-
113-
// where the PATH is a JSONPath string that locates the first occurance.
114-
115-
// So,
116-
117-
// var a = [];
118-
// a[0] = a;
119-
// return JSON.stringify(JSON.decycle(a));
120-
121-
// produces the string '[{"$ref":"$"}]'.
122-
123-
// If a replacer function is provided, then it will be called for each value.
124-
// A replacer function receives a value and returns a replacement value.
125-
126-
// JSONPath is used to locate the unique object. $ indicates the top level of
127-
// the object or array. [NUMBER] or [STRING] indicates a child element or
128-
// property.
129-
130-
const objects = new WeakMap<object, string>(); // object to path mappings
131-
132-
return deepCopy(object, '$', objects, replacer);
133-
}

0 commit comments

Comments
 (0)