-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding object deepFreeze and deepSeal
- Loading branch information
Showing
5 changed files
with
222 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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({}), {}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |