Skip to content

Commit

Permalink
adding object deepFreeze and deepSeal
Browse files Browse the repository at this point in the history
  • Loading branch information
Pinta365 committed Aug 1, 2024
1 parent 09a33fb commit 0c23d28
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 1 deletion.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ bunx jsr add @cross/utils
variable. Could be a version independent link such as `...nvm/current...`
instead of `...nvm/<version>...`.

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

**Classes**

- **Colors**
Expand Down Expand Up @@ -168,3 +180,34 @@ functionality:
// Spawn a child process with the same runtime:
const childProcess = spawn(runtimeExecPath, ["other-script.js"]);
```

- **@cross/utils/objectManip**
- **deepFreeze(obj: T, createCopy?: boolean): T** - Recursively freezes an
object and all its nested objects.
- **deepSeal(obj: T, createCopy?: boolean): T** - Recursively seals an object
and all its nested objects.
- **Examples:**
```javascript
// deepFreeze
const obj = { a: 1, b: { c: 2 } };
deepFreeze(obj); // obj is now frozen
obj.a = 10; // Throws an error in strict mode
const original = { x: 5, y: { z: 6 } };
const frozenCopy = deepFreeze(original, true); // frozenCopy is a new frozen object
frozenCopy.x = 20; // Throws an error in strict mode
original.x = 20; // Succeeds, original is unchanged
// deepSeal
const obj = { a: 1, b: { c: 2 } };
deepSeal(obj); // obj is now sealed
obj.a = 10; // Succeeds because 'a' is writable
obj.d = 4; // Throws an error in strict mode
delete obj.a; // Throws an error in strict mode
const original = { x: 5, y: { z: 6 } };
const sealedCopy = deepSeal(original, true); // sealedCopy is a new sealed object
sealedCopy.x = 20; // Succeeds because 'x' is writable
sealedCopy.w = 7; // Throws an error in strict mode
original.w = 20; // Succeeds, original is unchanged
```
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"./spawn": "./utils/spawn.ts",
"./table": "./utils/table.ts",
"./execpath": "./utils/execpath.ts",
"./sysinfo": "./utils/sysinfo.ts"
"./sysinfo": "./utils/sysinfo.ts",
"./objectManip": "./utils/objectManip.ts"
},
"imports": {
"@cross/fs": "jsr:@cross/fs@^0.1.11",
Expand Down
1 change: 1 addition & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export {
systemMemoryInfo,
uptime,
} from "./utils/sysinfo.ts";
export { deepFreeze, deepSeal } from "./utils/objectManip.ts";
88 changes: 88 additions & 0 deletions utils/objectManip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { assertEquals, assertThrows } from "@std/assert";
import { test } from "@cross/test";
import { deepFreeze, deepSeal } from "./objectManip.ts";

// deepFreeze Tests

test("deepFreeze() freezes object and nested objects", () => {
const obj = { a: 1, b: { c: 2 } };
const frozenObj = deepFreeze(obj);

assertEquals(Object.isFrozen(frozenObj), true);
assertEquals(Object.isFrozen(frozenObj.b), true);

assertThrows(() => {
frozenObj.a = 10;
}, TypeError);
assertThrows(() => {
frozenObj.b.c = 3;
}, TypeError);
});

test("deepFreeze(createCopy = true) returns a frozen copy, leaves original unchanged", () => {
const original = { x: 5, y: { z: 6 } };
const frozenCopy = deepFreeze(original, true);

assertEquals(Object.isFrozen(frozenCopy), true);
assertEquals(original.x, 5); // Original is not frozen

assertThrows(() => {
frozenCopy.x = 20;
}, TypeError);
original.x = 20; // Succeeds for the original
});

test("deepFreeze() handles null and non-objects", () => {
assertEquals(deepFreeze(null), null);
assertEquals(deepFreeze(undefined), undefined);
assertEquals(deepFreeze(5), 5);
});

// deepSeal Tests

test("deepSeal() seals object and nested objects", () => {
interface TestObject {
a?: number;
b: { c: number };
d?: number;
}

const obj: TestObject = { a: 1, b: { c: 2 } };
const sealedObj: TestObject = deepSeal(obj);

assertEquals(Object.isSealed(sealedObj), true);
assertEquals(Object.isSealed(sealedObj.b), true);

assertThrows(() => {
sealedObj.d = 4;
}, TypeError);
assertThrows(() => {
delete sealedObj.a;
}, TypeError);
});

test("deepSeal(createCopy = true) returns a sealed copy, leaves original unchanged", () => {
// Test insterface that allows for the test cases.
interface TestObject {
x?: number;
y: { z: number };
w?: number;
}
const original: TestObject = { x: 5, y: { z: 6 } };
const sealedCopy: TestObject = deepSeal(original, true);

assertEquals(Object.isSealed(sealedCopy), true);
assertEquals(original.x, 5); // Original is not sealed

assertThrows(() => {
sealedCopy.w = 7;
}, TypeError);
original.w = 7; // Succeeds for the original
});

test("deepSeal() handles null and non-objects", () => {
assertEquals(deepSeal(null), null);
assertEquals(deepSeal(undefined), undefined);
assertEquals(deepSeal(5), 5);
assertEquals(deepSeal({}), {});
});
88 changes: 88 additions & 0 deletions utils/objectManip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Functions that handle object manipulation.
*/

/**
* Recursively freezes an object and all its nested objects.
* Freezing an object prevents any modifications to its properties.
*
* @template T - The type of the object to freeze.
* @param {T} obj - The object to freeze.
* @param {boolean} [createCopy=false] - If true, returns a frozen deep copy of the object, leaving the original unchanged.
* @returns {T} The frozen object (original if `createCopy` is false, copy otherwise).
*
* @example
* const obj = { a: 1, b: { c: 2 } };
* deepFreeze(obj); // obj is now frozen
* obj.a = 10; // Throws an error in strict mode
*
* const original = { x: 5, y: { z: 6 } };
* const frozenCopy = deepFreeze(original, true); // frozenCopy is a new frozen object
* frozenCopy.x = 20; // Throws an error in strict mode
* original.x = 20; // Succeeds, original is unchanged
*/
export function deepFreeze<T>(obj: T, createCopy = false): T {
if (typeof obj !== "object" || obj === null) {
return obj;
}

const target = createCopy ? structuredClone(obj) : obj;
Object.freeze(target);

for (const prop in target) {
if (Object.hasOwn(target, prop)) {
const value = target[prop];
if (
typeof value === "object" && value !== null && !Object.isFrozen(value)
) {
deepFreeze(value, createCopy);
}
}
}

return target;
}
/**
* Recursively seals an object and all its nested objects.
* Sealing an object prevents new properties from being added or removed,
* but existing properties can still be modified if they are writable.
*
* @template T - The type of the object to seal.
* @param {T} obj - The object to seal.
* @param {boolean} [createCopy=false] - If true, returns a sealed deep copy of the object, leaving the original unchanged.
* @returns {T} The sealed object (original if `createCopy` is false, copy otherwise).
*
* @example
* const obj = { a: 1, b: { c: 2 } };
* deepSeal(obj); // obj is now sealed
* obj.a = 10; // Succeeds because 'a' is writable
* obj.d = 4; // Throws an error in strict mode
* delete obj.a; // Throws an error in strict mode
*
* const original = { x: 5, y: { z: 6 } };
* const sealedCopy = deepSeal(original, true); // sealedCopy is a new sealed object
* sealedCopy.x = 20; // Succeeds because 'x' is writable
* sealedCopy.w = 7; // Throws an error in strict mode
* original.w = 20; // Succeeds, original is unchanged
*/
export function deepSeal<T>(obj: T, createCopy = false): T {
if (typeof obj !== "object" || obj === null) {
return obj;
}

const target = createCopy ? structuredClone(obj) : obj;
Object.seal(target);

for (const prop in target) {
if (Object.hasOwn(target, prop)) {
const value = target[prop];
if (
typeof value === "object" && value !== null && !Object.isSealed(value)
) {
deepSeal(value, false);
}
}
}

return target;
}

0 comments on commit 0c23d28

Please sign in to comment.