Skip to content

Commit 0c23d28

Browse files
committed
adding object deepFreeze and deepSeal
1 parent 09a33fb commit 0c23d28

File tree

5 files changed

+222
-1
lines changed

5 files changed

+222
-1
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ bunx jsr add @cross/utils
5353
variable. Could be a version independent link such as `...nvm/current...`
5454
instead of `...nvm/<version>...`.
5555

56+
- **deepFreeze<T>(obj: T, createCopy?: boolean): T**
57+
- Recursively freezes an object and all its nested objects. Freezing prevents
58+
any modifications to the object's properties.
59+
- If `createCopy` is `true` (default is `false`), a new frozen deep copy of
60+
the object is returned, leaving the original unchanged.
61+
- **deepSeal<T>(obj: T, createCopy?: boolean): T**
62+
- Recursively seals an object and all its nested objects. Sealing prevents new
63+
properties from being added or removed, but existing properties can still be
64+
modified.
65+
- If `createCopy` is `true` (default is `false`), a new sealed deep copy of
66+
the object is returned, leaving the original unchanged.
67+
5668
**Classes**
5769

5870
- **Colors**
@@ -168,3 +180,34 @@ functionality:
168180
// Spawn a child process with the same runtime:
169181
const childProcess = spawn(runtimeExecPath, ["other-script.js"]);
170182
```
183+
184+
- **@cross/utils/objectManip**
185+
- **deepFreeze(obj: T, createCopy?: boolean): T** - Recursively freezes an
186+
object and all its nested objects.
187+
- **deepSeal(obj: T, createCopy?: boolean): T** - Recursively seals an object
188+
and all its nested objects.
189+
- **Examples:**
190+
```javascript
191+
// deepFreeze
192+
const obj = { a: 1, b: { c: 2 } };
193+
deepFreeze(obj); // obj is now frozen
194+
obj.a = 10; // Throws an error in strict mode
195+
196+
const original = { x: 5, y: { z: 6 } };
197+
const frozenCopy = deepFreeze(original, true); // frozenCopy is a new frozen object
198+
frozenCopy.x = 20; // Throws an error in strict mode
199+
original.x = 20; // Succeeds, original is unchanged
200+
201+
// deepSeal
202+
const obj = { a: 1, b: { c: 2 } };
203+
deepSeal(obj); // obj is now sealed
204+
obj.a = 10; // Succeeds because 'a' is writable
205+
obj.d = 4; // Throws an error in strict mode
206+
delete obj.a; // Throws an error in strict mode
207+
208+
const original = { x: 5, y: { z: 6 } };
209+
const sealedCopy = deepSeal(original, true); // sealedCopy is a new sealed object
210+
sealedCopy.x = 20; // Succeeds because 'x' is writable
211+
sealedCopy.w = 7; // Throws an error in strict mode
212+
original.w = 20; // Succeeds, original is unchanged
213+
```

deno.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"./spawn": "./utils/spawn.ts",
1111
"./table": "./utils/table.ts",
1212
"./execpath": "./utils/execpath.ts",
13-
"./sysinfo": "./utils/sysinfo.ts"
13+
"./sysinfo": "./utils/sysinfo.ts",
14+
"./objectManip": "./utils/objectManip.ts"
1415
},
1516
"imports": {
1617
"@cross/fs": "jsr:@cross/fs@^0.1.11",

mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export {
1313
systemMemoryInfo,
1414
uptime,
1515
} from "./utils/sysinfo.ts";
16+
export { deepFreeze, deepSeal } from "./utils/objectManip.ts";

utils/objectManip.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { assertEquals, assertThrows } from "@std/assert";
2+
import { test } from "@cross/test";
3+
import { deepFreeze, deepSeal } from "./objectManip.ts";
4+
5+
// deepFreeze Tests
6+
7+
test("deepFreeze() freezes object and nested objects", () => {
8+
const obj = { a: 1, b: { c: 2 } };
9+
const frozenObj = deepFreeze(obj);
10+
11+
assertEquals(Object.isFrozen(frozenObj), true);
12+
assertEquals(Object.isFrozen(frozenObj.b), true);
13+
14+
assertThrows(() => {
15+
frozenObj.a = 10;
16+
}, TypeError);
17+
assertThrows(() => {
18+
frozenObj.b.c = 3;
19+
}, TypeError);
20+
});
21+
22+
test("deepFreeze(createCopy = true) returns a frozen copy, leaves original unchanged", () => {
23+
const original = { x: 5, y: { z: 6 } };
24+
const frozenCopy = deepFreeze(original, true);
25+
26+
assertEquals(Object.isFrozen(frozenCopy), true);
27+
assertEquals(original.x, 5); // Original is not frozen
28+
29+
assertThrows(() => {
30+
frozenCopy.x = 20;
31+
}, TypeError);
32+
original.x = 20; // Succeeds for the original
33+
});
34+
35+
test("deepFreeze() handles null and non-objects", () => {
36+
assertEquals(deepFreeze(null), null);
37+
assertEquals(deepFreeze(undefined), undefined);
38+
assertEquals(deepFreeze(5), 5);
39+
});
40+
41+
// deepSeal Tests
42+
43+
test("deepSeal() seals object and nested objects", () => {
44+
interface TestObject {
45+
a?: number;
46+
b: { c: number };
47+
d?: number;
48+
}
49+
50+
const obj: TestObject = { a: 1, b: { c: 2 } };
51+
const sealedObj: TestObject = deepSeal(obj);
52+
53+
assertEquals(Object.isSealed(sealedObj), true);
54+
assertEquals(Object.isSealed(sealedObj.b), true);
55+
56+
assertThrows(() => {
57+
sealedObj.d = 4;
58+
}, TypeError);
59+
assertThrows(() => {
60+
delete sealedObj.a;
61+
}, TypeError);
62+
});
63+
64+
test("deepSeal(createCopy = true) returns a sealed copy, leaves original unchanged", () => {
65+
// Test insterface that allows for the test cases.
66+
interface TestObject {
67+
x?: number;
68+
y: { z: number };
69+
w?: number;
70+
}
71+
const original: TestObject = { x: 5, y: { z: 6 } };
72+
const sealedCopy: TestObject = deepSeal(original, true);
73+
74+
assertEquals(Object.isSealed(sealedCopy), true);
75+
assertEquals(original.x, 5); // Original is not sealed
76+
77+
assertThrows(() => {
78+
sealedCopy.w = 7;
79+
}, TypeError);
80+
original.w = 7; // Succeeds for the original
81+
});
82+
83+
test("deepSeal() handles null and non-objects", () => {
84+
assertEquals(deepSeal(null), null);
85+
assertEquals(deepSeal(undefined), undefined);
86+
assertEquals(deepSeal(5), 5);
87+
assertEquals(deepSeal({}), {});
88+
});

utils/objectManip.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Functions that handle object manipulation.
3+
*/
4+
5+
/**
6+
* Recursively freezes an object and all its nested objects.
7+
* Freezing an object prevents any modifications to its properties.
8+
*
9+
* @template T - The type of the object to freeze.
10+
* @param {T} obj - The object to freeze.
11+
* @param {boolean} [createCopy=false] - If true, returns a frozen deep copy of the object, leaving the original unchanged.
12+
* @returns {T} The frozen object (original if `createCopy` is false, copy otherwise).
13+
*
14+
* @example
15+
* const obj = { a: 1, b: { c: 2 } };
16+
* deepFreeze(obj); // obj is now frozen
17+
* obj.a = 10; // Throws an error in strict mode
18+
*
19+
* const original = { x: 5, y: { z: 6 } };
20+
* const frozenCopy = deepFreeze(original, true); // frozenCopy is a new frozen object
21+
* frozenCopy.x = 20; // Throws an error in strict mode
22+
* original.x = 20; // Succeeds, original is unchanged
23+
*/
24+
export function deepFreeze<T>(obj: T, createCopy = false): T {
25+
if (typeof obj !== "object" || obj === null) {
26+
return obj;
27+
}
28+
29+
const target = createCopy ? structuredClone(obj) : obj;
30+
Object.freeze(target);
31+
32+
for (const prop in target) {
33+
if (Object.hasOwn(target, prop)) {
34+
const value = target[prop];
35+
if (
36+
typeof value === "object" && value !== null && !Object.isFrozen(value)
37+
) {
38+
deepFreeze(value, createCopy);
39+
}
40+
}
41+
}
42+
43+
return target;
44+
}
45+
/**
46+
* Recursively seals an object and all its nested objects.
47+
* Sealing an object prevents new properties from being added or removed,
48+
* but existing properties can still be modified if they are writable.
49+
*
50+
* @template T - The type of the object to seal.
51+
* @param {T} obj - The object to seal.
52+
* @param {boolean} [createCopy=false] - If true, returns a sealed deep copy of the object, leaving the original unchanged.
53+
* @returns {T} The sealed object (original if `createCopy` is false, copy otherwise).
54+
*
55+
* @example
56+
* const obj = { a: 1, b: { c: 2 } };
57+
* deepSeal(obj); // obj is now sealed
58+
* obj.a = 10; // Succeeds because 'a' is writable
59+
* obj.d = 4; // Throws an error in strict mode
60+
* delete obj.a; // Throws an error in strict mode
61+
*
62+
* const original = { x: 5, y: { z: 6 } };
63+
* const sealedCopy = deepSeal(original, true); // sealedCopy is a new sealed object
64+
* sealedCopy.x = 20; // Succeeds because 'x' is writable
65+
* sealedCopy.w = 7; // Throws an error in strict mode
66+
* original.w = 20; // Succeeds, original is unchanged
67+
*/
68+
export function deepSeal<T>(obj: T, createCopy = false): T {
69+
if (typeof obj !== "object" || obj === null) {
70+
return obj;
71+
}
72+
73+
const target = createCopy ? structuredClone(obj) : obj;
74+
Object.seal(target);
75+
76+
for (const prop in target) {
77+
if (Object.hasOwn(target, prop)) {
78+
const value = target[prop];
79+
if (
80+
typeof value === "object" && value !== null && !Object.isSealed(value)
81+
) {
82+
deepSeal(value, false);
83+
}
84+
}
85+
}
86+
87+
return target;
88+
}

0 commit comments

Comments
 (0)