From 0d1451eb84441b0abea318821b7f556e31e0d61d Mon Sep 17 00:00:00 2001 From: Thanga Ganapathy Date: Mon, 29 Apr 2024 11:10:11 +0530 Subject: [PATCH] feat(Object): add mutable set & unset variants --- .changeset/yellow-jobs-tell.md | 5 ++ .github/workflows/release.yml | 2 +- README.md | 2 +- apps/docs/pages/Object/_meta.json | 2 + apps/docs/pages/Object/set.mdx | 12 +++- apps/docs/pages/Object/toSet.mdx | 70 +++++++++++++++++++ apps/docs/pages/Object/toUnset.mdx | 60 ++++++++++++++++ apps/docs/pages/Object/unset.mdx | 32 +++++---- apps/docs/pages/index.mdx | 21 +++++- benchmark.js | 4 +- packages/std/README.md | 2 +- packages/std/__tests__/object/toSet.spec.ts | 55 +++++++++++++++ packages/std/__tests__/object/toUnset.spec.ts | 58 +++++++++++++++ packages/std/__tests__/object/unset.spec.ts | 47 ++++++++++--- packages/std/jsr.json | 2 +- packages/std/src/assert/isEql.ts | 5 +- packages/std/src/index.ts | 2 + packages/std/src/object/set.ts | 6 +- packages/std/src/object/toSet.ts | 45 ++++++++++++ packages/std/src/object/toUnset.ts | 48 +++++++++++++ packages/std/src/object/unset.ts | 20 +++--- 21 files changed, 450 insertions(+), 50 deletions(-) create mode 100644 .changeset/yellow-jobs-tell.md create mode 100644 apps/docs/pages/Object/toSet.mdx create mode 100644 apps/docs/pages/Object/toUnset.mdx create mode 100644 packages/std/__tests__/object/toSet.spec.ts create mode 100644 packages/std/__tests__/object/toUnset.spec.ts create mode 100644 packages/std/src/object/toSet.ts create mode 100644 packages/std/src/object/toUnset.ts diff --git a/.changeset/yellow-jobs-tell.md b/.changeset/yellow-jobs-tell.md new file mode 100644 index 0000000..c08fd36 --- /dev/null +++ b/.changeset/yellow-jobs-tell.md @@ -0,0 +1,5 @@ +--- +"@opentf/std": minor +--- + +Added mutable set & unset variant functions. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc95f36..9dfe66b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: branches: - main paths-ignore: - - "apps/website/**" + - "apps/docs/**" concurrency: ${{ github.workflow }}-${{ github.ref }} diff --git a/README.md b/README.md index ba9e3a8..6811ada 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ isEql: - The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & same invalid dates. - The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays. - - The lodash `isEqual` does not check `Map` ordering & object values in `Set`. + - The lodash `isEqual` having issues with Map & does not check `Map` ordering & object values in `Set`. - The ramda `equals` does not check `Map` ordering & symbols. - The dequal does not support cyclic refs, Map ordering, symbols & same invalid dates. ``` diff --git a/apps/docs/pages/Object/_meta.json b/apps/docs/pages/Object/_meta.json index 8a9e15d..df1f830 100644 --- a/apps/docs/pages/Object/_meta.json +++ b/apps/docs/pages/Object/_meta.json @@ -12,5 +12,7 @@ "shallowMergeAll": "shallowMergeAll", "size": "size", "toPath": "toPath", + "toSet": "toSet", + "toUnset": "toUnset", "unset": "unset" } diff --git a/apps/docs/pages/Object/set.mdx b/apps/docs/pages/Object/set.mdx index 5c4a4f9..b455b08 100644 --- a/apps/docs/pages/Object/set.mdx +++ b/apps/docs/pages/Object/set.mdx @@ -3,10 +3,18 @@ import REPL from "../../components/REPL"; > Sets the value to an object at the given path. - -Immutable: This does not mutate the original object. + +This mutates the original object. +### Related + +- [get](/Object/get) +- [has](/Object/has) +- [unset](/Object/unset) +- [toSet](/Object/toSet) +- [toUnset](/Object/toUnset) + ## Syntax ```ts diff --git a/apps/docs/pages/Object/toSet.mdx b/apps/docs/pages/Object/toSet.mdx new file mode 100644 index 0000000..64d5028 --- /dev/null +++ b/apps/docs/pages/Object/toSet.mdx @@ -0,0 +1,70 @@ +import { Callout } from "nextra/components"; +import REPL from "../../components/REPL"; + +> Sets the value to an object at the given path. + + +Immutable: This does not mutate the original object. + + +### Related + +- [get](/Object/get) +- [set](/Object/set) +- [has](/Object/has) +- [unset](/Object/unset) +- [toUnset](/Object/toUnset) + +## Syntax + +```ts +import { toSet } from '@opentf/std'; + +toSet( + obj: T, + path: string | unknown[], + value: unknown | ((val: unknown) => unknown) +): T +``` + +The value param can be either any `value` or `callback` function. + + + +The `callback` fn can be called with the property path value if it exist. + + +## Examples + +```ts +toSet({}, 'a', null) //=> { a: null } + +toSet({}, 'a', 1) //=> { a: 1 } + +toSet({}, 'a.b', 25) //=> { a: { b: 25 } } + +toSet({}, 'user.email', 'user@example.com') +//=> +// { +// user: { email: 'user@example.com' } +// } + +toSet({}, '0', 'Apple') //=> { '0': 'Apple' } + +toSet({}, 'fruits[0]', 'Apple') //=> { fruits: ['Apple'] } + +toSet({ a: 1 }, 'a', (val) => val + 1) //=> { a: 2 } + +const fn = () => render('My Component') +toSet({ subscribeFns: [] }, 'subscribeFns[0]', () => fn) +//=> { subscribeFns: [fn] } +``` + +## Try + + val + 1) +`} /> \ No newline at end of file diff --git a/apps/docs/pages/Object/toUnset.mdx b/apps/docs/pages/Object/toUnset.mdx new file mode 100644 index 0000000..b420edc --- /dev/null +++ b/apps/docs/pages/Object/toUnset.mdx @@ -0,0 +1,60 @@ +import { Callout } from "nextra/components"; +import REPL from "../../components/REPL"; + +> Removes the property of the given object at the given path. + + +Immutable: This does not mutate the original object. + + + +If an array value is removed, then it will not return a [sparsed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays) array. + + +### Related + +- [get](/Object/get) +- [has](/Object/has) +- [set](/Object/set) +- [unset](/Object/unset) +- [toSet](/Object/toSet) + +## Syntax + +```ts +import { toUnset } from '@opentf/std'; + +toUnset( + obj: T, + path: string | unknown[], +): T +``` + +## Examples + +```ts +const obj = { a: 1, b: 2 }; +toUnset(obj, 'a') //=> { b: 2 } + +toUnset(obj, 'b') //=> { a: 1 } + +toUnset(obj, 'c') //=> { a: 1, b: 2 } + +const arr = [1, 2, 3]; +toUnset(arr, '0') //=> [2, 3] + +toUnset(arr, '1') //=> [1, 3] + +toUnset(arr, '3') //=> [1, 2, 3] + +const nestedObj = { x: { y: { z: ['a', null, 'b'] } } }; +toUnset(nestedObj, 'x.y.z') //=> { x: { y: {} } } +``` + +## Try + + \ No newline at end of file diff --git a/apps/docs/pages/Object/unset.mdx b/apps/docs/pages/Object/unset.mdx index 81ce267..c512546 100644 --- a/apps/docs/pages/Object/unset.mdx +++ b/apps/docs/pages/Object/unset.mdx @@ -3,13 +3,18 @@ import REPL from "../../components/REPL"; > Removes the property of the given object at the given path. - -Immutable: This does not mutate the original object. + +This mutates the original object. - -If an array value is removed, then it will not return a [sparsed](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays) array. - +### Related + +- [get](/Object/get) +- [has](/Object/has) +- [set](/Object/set) +- [toSet](/Object/toSet) +- [toUnset](/Object/toUnset) + ## Syntax @@ -25,19 +30,20 @@ unset( ## Examples ```ts -const obj = { a: 1, b: 2 }; -unset(obj, 'a') //=> { b: 2 } +unset({ a: 1, b: 2 }, 'a') //=> { b: 2 } + +unset({ a: 1, b: 2 }, 'b') //=> { a: 1 } + +unset({ a: 1, b: 2 }, 'c') //=> { a: 1, b: 2 } -unset(obj, 'b') //=> { a: 1 } -unset(obj, 'c') //=> { a: 1, b: 2 } +unset([1, 2, 3], '0') //=> [<1 Empty>, 2, 3] -const arr = [1, 2, 3]; -unset(arr, '0') //=> [2, 3] +unset([1, 2, 3], '1') //=> [1, <1 Empty>, 3] -unset(arr, '1') //=> [1, 3] +unset([1, 2, 3], '2') //=> [1, 2, <1 Empty>] -unset(arr, '3') //=> [1, 2, 3] +unset([1, 2, 3], '3') //=> [1, 2, 3] const nestedObj = { x: { y: { z: ['a', null, 'b'] } } }; unset(nestedObj, 'x.y.z') //=> { x: { y: {} } } diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 81fe27b..4dc7d1a 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -53,6 +53,9 @@ Let’s explore some of the library’s capabilities: import { isNum } from "@opentf/std"; isNum(NaN); //=> false +isNum('1'); //=> false +isNum('1', true); //=> true +isNum(1); //=> true ``` 2. Converting Strings to Pascal Case: @@ -84,11 +87,15 @@ clone(obj); // Returns deeply cloned value ```js import { isEql, isEqlArr } from "@opentf/std"; +isEql({a: 1}, {a: 1}); //=> true + const mapA = new Map([["a", 1], ["b", 2]]); const mapB = new Map([["b", 2], ["a", 1]]); isEql(mapA, mapB); //=> false +// Compare Arrays ignoring order isEqlArr([1, 2, 3], [2, 3, 1]); //=> true +isEqlArr([1, 2, 3], [1, 2, 5]); //=> false ``` 6. Adding a Delay (1 second) with sleep: @@ -117,6 +124,18 @@ compose( ); //=> 6 ``` +8. Pick & Omit Paths in an Object + +```js +import { pick omit } from '@opentf/std'; + +const obj = { a: 1, b: 2, c: 3 }; + +pick(obj, 'a', 'c'); //=> { a: 1, c: 3 } + +omit(obj, 'a', 'c'); //=> { b: 2 } +``` + You can try out these examples on the [Playground](/playground). @@ -183,7 +202,7 @@ isEql: - The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & same invalid dates. - The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays. - - The lodash `isEqual` does not check `Map` ordering & object values in `Set`. + - The lodash `isEqual` having issues with Map & does not check `Map` ordering & object values in `Set`. - The ramda `equals` does not check `Map` ordering & symbols. - The dequal does not support cyclic refs, Map ordering, symbols & same invalid dates. ``` diff --git a/benchmark.js b/benchmark.js index 963f183..01c8b14 100644 --- a/benchmark.js +++ b/benchmark.js @@ -172,6 +172,6 @@ async function isEqlBench() { `); } -await cloneBench(); +// await cloneBench(); // await sortByBench(); -// await isEqlBench(); +await isEqlBench(); diff --git a/packages/std/README.md b/packages/std/README.md index 104d387..b2e0429 100644 --- a/packages/std/README.md +++ b/packages/std/README.md @@ -338,7 +338,7 @@ isEql: - The native util `deepStrictEqual` not available in browsers, does not check `Map` ordering & same invalid dates. - The `fast-deep-equal/es6` does not support cyclic refs, Map ordering check, invalid dates, symbols, objects values in Set & TypedArrays. - - The lodash `isEqual` does not check `Map` ordering & object values in `Set`. + - The lodash `isEqual` having issues with Map & does not check `Map` ordering & object values in `Set`. - The ramda `equals` does not check `Map` ordering & symbols. - The dequal does not support cyclic refs, Map ordering, symbols & same invalid dates. ``` diff --git a/packages/std/__tests__/object/toSet.spec.ts b/packages/std/__tests__/object/toSet.spec.ts new file mode 100644 index 0000000..646b2f7 --- /dev/null +++ b/packages/std/__tests__/object/toSet.spec.ts @@ -0,0 +1,55 @@ +import { toSet } from '../../src'; + +describe('Object', () => { + test('toSet', () => { + expect(toSet({}, 'a', null)).toEqual({ a: null }); + + expect(toSet({}, 'a', 1)).toEqual({ a: 1 }); + + expect(toSet({}, 'a.b', 25)).toEqual({ a: { b: 25 } }); + + expect(toSet({}, 'user.email', 'user@example.com')).toEqual({ + user: { email: 'user@example.com' }, + }); + + const obj = { name: 'x' }; + const newObj = toSet(obj, 'name', 'xxx'); + expect(newObj.name).toBe('xxx'); + + expect(toSet({}, '0', 'Apple')).toEqual({ + '0': 'Apple', + }); + + expect(toSet({}, 'fruits[0]', 'Apple')).toEqual({ + fruits: ['Apple'], + }); + + expect(toSet({ fruits: ['Apple'] }, 'fruits[0]', 'Mango')).toEqual({ + fruits: ['Mango'], + }); + + expect(toSet({ fruits: ['Apple'] }, 'fruits[1]', 'Mango')).toEqual({ + fruits: ['Apple', 'Mango'], + }); + + expect(toSet({ a: [{ b: { c: 3 } }] }, 'a[0].b.c', 4)).toEqual({ + a: [{ b: { c: 4 } }], + }); + }); + + test('updating values', () => { + expect(toSet({}, 'a', () => 1)).toEqual({ a: 1 }); + expect(toSet({ a: 1 }, 'a', (val) => val + 1)).toEqual({ a: 2 }); + expect( + toSet({ a: 1, b: [2] }, 'b', (arr) => { + arr.unshift(1); + return arr; + }) + ).toEqual({ + a: 1, + b: [1, 2], + }); + const fn = (a, b) => a ** b; + expect(toSet({ a: 1 }, 'b', (val) => fn)).toEqual({ a: 1, b: fn }); + }); +}); diff --git a/packages/std/__tests__/object/toUnset.spec.ts b/packages/std/__tests__/object/toUnset.spec.ts new file mode 100644 index 0000000..50fb540 --- /dev/null +++ b/packages/std/__tests__/object/toUnset.spec.ts @@ -0,0 +1,58 @@ +import { toUnset } from '../../src'; + +describe('Object => toUnset', () => { + test('non object', () => { + expect(toUnset(undefined, 'a')).toBe(undefined); + expect(toUnset(null, 'a')).toBe(null); + expect(toUnset(1, 'a')).toBe(1); + expect(toUnset('test', '0')).toBe('test'); + expect(toUnset({}, 'a')).toEqual({}); + expect(toUnset({ a: 2 }, 'b')).toEqual({ a: 2 }); + expect(toUnset({ a: { b: [1, 2, 3] } }, 'a.b.3')).toEqual({ + a: { b: [1, 2, 3] }, + }); + }); + + test('Simple', () => { + const obj = { a: 1, b: 2 }; + expect(toUnset(obj, 'a')).toEqual({ b: 2 }); + expect(toUnset(obj, 'b')).toEqual({ a: 1 }); + expect(toUnset(obj, 'c')).toEqual({ a: 1, b: 2 }); + }); + + test('Nested', () => { + const obj = { a: [{ b: { c: 7 } }] }; + expect(toUnset(obj, 'a[0].b.c')).toEqual({ a: [{ b: {} }] }); + expect(toUnset(obj, ['a', '0'])).toEqual({ a: [] }); + + const objWithNestedArray = { x: { y: { z: ['a', null, 'b'] } } }; + expect(toUnset(objWithNestedArray, 'x.y.z')).toEqual({ x: { y: {} } }); + expect(toUnset(objWithNestedArray, 'x.y.z.0')).toEqual({ + x: { y: { z: [null, 'b'] } }, + }); + expect(toUnset(objWithNestedArray, 'x.y.z.1')).toEqual({ + x: { y: { z: ['a', 'b'] } }, + }); + }); + + test('Array', () => { + const obj = [1, 2, 3]; + expect(toUnset(obj, '0')).toEqual([2, 3]); + expect(toUnset(obj, '1')).toEqual([1, 3]); + expect(toUnset(obj, '3')).toEqual([1, 2, 3]); + }); + + test('invalid paths', () => { + expect(toUnset({ a: 1 }, 'b')).toEqual({ a: 1 }); + expect(toUnset({ a: null }, 'a.b')).toEqual({ a: null }); + expect(toUnset({ a: { b: [1, 2, 3] } }, 'a.c')).toEqual({ + a: { b: [1, 2, 3] }, + }); + expect(toUnset({ a: { b: [1, 2, 3] } }, 'a.b.c')).toEqual({ + a: { b: [1, 2, 3] }, + }); + expect(toUnset({ a: { b: [1, 2, 3] } }, 'a.b.3')).toEqual({ + a: { b: [1, 2, 3] }, + }); + }); +}); diff --git a/packages/std/__tests__/object/unset.spec.ts b/packages/std/__tests__/object/unset.spec.ts index 13bbaa4..46a7723 100644 --- a/packages/std/__tests__/object/unset.spec.ts +++ b/packages/std/__tests__/object/unset.spec.ts @@ -1,11 +1,23 @@ import { unset } from '../../src'; describe('Object => unset', () => { + test('non object', () => { + expect(unset(undefined, 'a')).toBe(undefined); + expect(unset(null, 'a')).toBe(null); + expect(unset(1, 'a')).toBe(1); + expect(unset('test', '0')).toBe('test'); + expect(unset({}, 'a')).toEqual({}); + expect(unset({ a: 2 }, 'b')).toEqual({ a: 2 }); + expect(unset({ a: { b: [1, 2, 3] } }, 'a.b.3')).toEqual({ + a: { b: [1, 2, 3] }, + }); + }); + test('Simple', () => { const obj = { a: 1, b: 2 }; expect(unset(obj, 'a')).toEqual({ b: 2 }); - expect(unset(obj, 'b')).toEqual({ a: 1 }); - expect(unset(obj, 'c')).toEqual({ a: 1, b: 2 }); + expect(unset(obj, 'b')).toEqual({}); + expect(unset({ a: 1, b: 2 }, 'c')).toEqual({ a: 1, b: 2 }); }); test('Nested', () => { @@ -14,19 +26,36 @@ describe('Object => unset', () => { expect(unset(obj, ['a', '0'])).toEqual({ a: [] }); const objWithNestedArray = { x: { y: { z: ['a', null, 'b'] } } }; + const objWithNestedArray2 = { x: { y: { z: ['a', null, 'b'] } } }; + const objWithNestedArray3 = { x: { y: { z: ['a', null, 'b'] } } }; expect(unset(objWithNestedArray, 'x.y.z')).toEqual({ x: { y: {} } }); - expect(unset(objWithNestedArray, 'x.y.z.0')).toEqual({ - x: { y: { z: [null, 'b'] } }, + expect(unset(objWithNestedArray2, 'x.y.z.0')).toEqual({ + x: { y: { z: [, null, 'b'] } }, }); - expect(unset(objWithNestedArray, 'x.y.z.1')).toEqual({ - x: { y: { z: ['a', 'b'] } }, + expect(unset(objWithNestedArray3, 'x.y.z.1')).toEqual({ + x: { y: { z: ['a', , 'b'] } }, }); }); test('Array', () => { const obj = [1, 2, 3]; - expect(unset(obj, '0')).toEqual([2, 3]); - expect(unset(obj, '1')).toEqual([1, 3]); - expect(unset(obj, '3')).toEqual([1, 2, 3]); + expect(unset(obj, '0')).toEqual([, 2, 3]); + expect(unset(obj, '1')).toEqual([, , 3]); + expect(unset(obj, '2')).toEqual([, , ,]); + expect(unset([1, 2, 3], '3')).toEqual([1, 2, 3]); + }); + + test('invalid paths', () => { + expect(unset({ a: 1 }, 'b')).toEqual({ a: 1 }); + expect(unset({ a: null }, 'a.b')).toEqual({ a: null }); + expect(unset({ a: { b: [1, 2, 3] } }, 'a.c')).toEqual({ + a: { b: [1, 2, 3] }, + }); + expect(unset({ a: { b: [1, 2, 3] } }, 'a.b.c')).toEqual({ + a: { b: [1, 2, 3] }, + }); + expect(unset({ a: { b: [1, 2, 3] } }, 'a.b.3')).toEqual({ + a: { b: [1, 2, 3] }, + }); }); }); diff --git a/packages/std/jsr.json b/packages/std/jsr.json index 9130823..77c01e8 100644 --- a/packages/std/jsr.json +++ b/packages/std/jsr.json @@ -1,5 +1,5 @@ { "name": "@opentf/std", - "version": "0.8.0", + "version": "0.11.0", "exports": "./src/index.ts" } diff --git a/packages/std/src/assert/isEql.ts b/packages/std/src/assert/isEql.ts index 7b43313..de15a30 100644 --- a/packages/std/src/assert/isEql.ts +++ b/packages/std/src/assert/isEql.ts @@ -38,10 +38,7 @@ function isEqlVal( } // Check both has same type string tag - if ( - Object.prototype.toString.call(val1) !== - Object.prototype.toString.call(val2) - ) { + if (typeof val1 !== typeof val2) { return false; } diff --git a/packages/std/src/index.ts b/packages/std/src/index.ts index c320e4b..e690088 100644 --- a/packages/std/src/index.ts +++ b/packages/std/src/index.ts @@ -95,7 +95,9 @@ export { default as isDataView } from './types/isDataView'; export { default as has } from './object/has'; export { default as get } from './object/get'; export { default as set } from './object/set'; +export { default as toSet } from './object/toSet'; export { default as unset } from './object/unset'; +export { default as toUnset } from './object/toUnset'; export { default as clone } from './object/clone'; export { default as size } from './object/size'; export { default as merge } from './object/merge'; diff --git a/packages/std/src/object/set.ts b/packages/std/src/object/set.ts index 61e5356..ad00b37 100644 --- a/packages/std/src/object/set.ts +++ b/packages/std/src/object/set.ts @@ -1,7 +1,6 @@ import isEmpty from '../assert/isEmpty'; import isFn from '../types/isFn'; import isNum from '../types/isNum'; -import clone from './clone'; import { IterableObj } from './merge'; import toPath from './toPath'; @@ -18,8 +17,7 @@ export default function set( value: unknown | ((val: unknown) => unknown) ): T { const pathArr = toPath(path); - const cObj = clone(obj); - let curObj: IterableObj = cObj as IterableObj; + let curObj: IterableObj = obj as IterableObj; if (isEmpty(pathArr)) { return obj; @@ -41,5 +39,5 @@ export default function set( curObj = curObj[prop] as IterableObj; } - return cObj as T; + return obj as T; } diff --git a/packages/std/src/object/toSet.ts b/packages/std/src/object/toSet.ts new file mode 100644 index 0000000..9d26ed7 --- /dev/null +++ b/packages/std/src/object/toSet.ts @@ -0,0 +1,45 @@ +import isEmpty from '../assert/isEmpty'; +import isFn from '../types/isFn'; +import isNum from '../types/isNum'; +import clone from './clone'; +import { IterableObj } from './merge'; +import toPath from './toPath'; + +/** + * Sets the value to an object at the given path & returns new object. + * + * @example + * + * set({}}, 'a.b', 1) //=> {a: {b: 1} } + */ +export default function toSet( + obj: T, + path: string | unknown[], + value: unknown | ((val: unknown) => unknown) +): T { + const pathArr = toPath(path); + const cObj = clone(obj); + let curObj: IterableObj = cObj as IterableObj; + + if (isEmpty(pathArr)) { + return obj; + } + + for (let i = 0; i < pathArr.length; i++) { + const prop = pathArr[i] as PropertyKey; + + if (i === pathArr.length - 1) { + const v = isFn(value) ? value(curObj[prop]) : value; + curObj[prop] = v; + break; + } + + if (!curObj[prop]) { + curObj[prop] = isNum(pathArr[i + 1], true) ? [] : {}; + } + + curObj = curObj[prop] as IterableObj; + } + + return cObj as T; +} diff --git a/packages/std/src/object/toUnset.ts b/packages/std/src/object/toUnset.ts new file mode 100644 index 0000000..0390ba2 --- /dev/null +++ b/packages/std/src/object/toUnset.ts @@ -0,0 +1,48 @@ +import isEmpty from '../assert/isEmpty'; +import isArr from '../types/isArr'; +import isNum from '../types/isNum'; +import isObj from '../types/isObj'; +import clone from './clone'; +import { IterableObj } from './merge'; +import toPath from './toPath'; + +/** + * Removes the property of the given object at the given path & returns new object. + * + * @example + * + * unset({a: 1, b: 2}}, 'a') //=> true + */ +export default function unset(obj: T, path: string | unknown[]): T { + const pathArr = toPath(path); + + if (isEmpty(pathArr) || !(isObj(obj) || isArr(obj))) { + return obj; + } + + const cObj = clone(obj); + let curObj: IterableObj = cObj as IterableObj; + + for (let i = 0; i < pathArr.length; i++) { + const prop = pathArr[i] as PropertyKey; + + if (i === pathArr.length - 1) { + if (isArr(curObj)) { + if (isNum(prop, true)) { + curObj.splice(prop, 1); + } + } else { + delete curObj[prop]; + } + break; + } + + curObj = curObj[prop] as IterableObj; + + if (!(isObj(curObj) || isArr(curObj))) { + return cObj; + } + } + + return cObj as T; +} diff --git a/packages/std/src/object/unset.ts b/packages/std/src/object/unset.ts index 81113f4..2ec574c 100644 --- a/packages/std/src/object/unset.ts +++ b/packages/std/src/object/unset.ts @@ -1,6 +1,6 @@ import isEmpty from '../assert/isEmpty'; import isArr from '../types/isArr'; -import clone from './clone'; +import isObj from '../types/isObj'; import { IterableObj } from './merge'; import toPath from './toPath'; @@ -13,10 +13,9 @@ import toPath from './toPath'; */ export default function unset(obj: T, path: string | unknown[]): T { const pathArr = toPath(path); - const cObj = clone(obj); - let curObj: IterableObj = cObj as IterableObj; + let curObj: IterableObj = obj as IterableObj; - if (isEmpty(pathArr)) { + if (isEmpty(pathArr) || !(isObj(obj) || isArr(obj))) { return obj; } @@ -24,17 +23,16 @@ export default function unset(obj: T, path: string | unknown[]): T { const prop = pathArr[i] as PropertyKey; if (i === pathArr.length - 1) { - if (isArr(curObj)) { - curObj.splice(prop as number, 1); - } else { - delete curObj[prop]; - } - + delete curObj[prop]; break; } curObj = curObj[prop] as IterableObj; + + if (!(isObj(curObj) || isArr(curObj))) { + return obj; + } } - return cObj as T; + return obj as T; }