feat(set): add set function (#223)

* feat: set

* test: set

* chore: add doc and benchmark

* chore: fix benchmark name
This commit is contained in:
novo 2024-07-18 10:00:59 +09:00 committed by GitHub
parent 7ee48c0e71
commit b9432e1cfd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 259 additions and 0 deletions

19
benchmarks/set.bench.ts Normal file
View File

@ -0,0 +1,19 @@
import { describe, bench } from 'vitest';
import { set } from 'es-toolkit';
import { set as lodashSet } from 'lodash';
describe('set', () => {
bench('es-toolkit/set-1', () => {
set({}, 'a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z', 1);
});
bench('es-toolkit/set-2', () => {
set({}, 'a[0][1][2][3][4][5][6]', 1);
});
bench('lodash/set-1', () => {
lodashSet({}, 'a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z', 1);
});
bench('lodash/set-2', () => {
lodashSet({}, 'a[0][1][2][3][4][5][6]', 1);
});
});

View File

@ -138,6 +138,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'pick', link: '/reference/object/pick' },
{ text: 'pickBy', link: '/reference/object/pickBy' },
{ text: 'invert', link: '/reference/object/invert' },
{ text: 'set', link: '/reference/object/set' },
],
},
{

View File

@ -149,6 +149,7 @@ function sidebar(): DefaultTheme.Sidebar {
{ text: 'pick', link: '/ko/reference/object/pick' },
{ text: 'pickBy', link: '/ko/reference/object/pickBy' },
{ text: 'invert', link: '/ko/reference/object/invert' },
{ text: 'set', link: '/ko/reference/object/set' },
],
},
{

View File

@ -0,0 +1,46 @@
# set
지정된 경로에 주어진 값을 설정해요. 경로의 일부가 존재하지 않으면 생성됩니다.
## Signature
```typescript
function set<T>(obj: Settable, path: Path, value: any): T;
```
### Parameters
- `obj` (Settable): 값을 설정할 객체예요.
- `path` (Path): 값을 설정할 속성의 경로예요.
- `value` (any): 설정할 값이에요.
### Returns
(`T`): 수정된 객체를 반환해요. T를 지정하지 않으면 unknown이에요.
## Examples
```typescript
// 중첩된 객체에 값 설정
const obj = { a: { b: { c: 3 } } };
set(obj, 'a.b.c', 4);
console.log(obj.a.b.c); // 4
// 배열에 값 설정
const arr = [1, 2, 3];
set(arr, 1, 4);
console.log(arr[1]); // 4
// 존재하지 않는 경로 생성 및 값 설정
const obj2 = {};
set(obj2, 'a.b.c', 4);
console.log(obj2); // { a: { b: { c: 4 } } }
// 인터페이스 사용
interface O {
a: number;
}
const obj3 = {};
const result = set<O>(obj3, 'a', 1); // result 타입 = { a: number }
console.log(result); // { a: 1 }
```

View File

@ -0,0 +1,46 @@
# set
Sets the given value at the specified path of the object. If any part of the path does not exist, it will be created.
## Signature
```typescript
function set<T>(obj: Settable, path: Path, value: any): T;
```
### Parameters
- `obj` (Settable): The object to modify.
- `path` (Path): The path of the property to set.
- `value` (any): The value to set.
### Returns
(`T`): Returns the modified object. If T is not specified, it defaults to unknown.
## Examples
```typescript
// Set a value in a nested object
const obj = { a: { b: { c: 3 } } };
set(obj, 'a.b.c', 4);
console.log(obj.a.b.c); // 4
// Set a value in an array
const arr = [1, 2, 3];
set(arr, 1, 4);
console.log(arr[1]); // 4
// Create non-existent path and set value
const obj2 = {};
set(obj2, 'a.b.c', 4);
console.log(obj2); // { a: { b: { c: 4 } } }
// Use with interface
interface O {
a: number;
}
const obj3 = {};
const result = set<O>(obj3, 'a', 1); // typeof result = { a: number }
console.log(result); // { a: 1 }
```

View File

@ -4,3 +4,4 @@ export { pick } from './pick.ts';
export { pickBy } from './pickBy.ts';
export { invert } from './invert.ts';
export { clone } from './clone.ts';
export { set } from './set.ts';

88
src/object/set.spec.ts Normal file
View File

@ -0,0 +1,88 @@
import { describe, it, expect } from 'vitest';
import { set } from './set';
describe('set', () => {
//--------------------------------------------------------------------------------
// object
//--------------------------------------------------------------------------------
it('should set a value on an object', () => {
interface Test {
a: number;
}
const result = set<Test>({}, 'a', 1);
result.a;
expect(result).toEqual({ a: 1 });
});
it('should set a value on an object with nested path', () => {
const result = set<{ a: { b: number } }>({}, 'a.b', 1);
expect(result).toEqual({ a: { b: 1 } });
});
it('should set a value on an object with nested path', () => {
const result = set<{ a: { b: { c: { d: number } } } }>({}, 'a.b.c.d', 1);
expect(result).toEqual({ a: { b: { c: { d: 1 } } } });
});
//--------------------------------------------------------------------------------
// array
//--------------------------------------------------------------------------------
it('should set a value on an array', () => {
const result = set<number[]>([], 0, 1);
expect(result).toEqual([1]);
expect(result[0]).toEqual(1);
});
it('should set a value on an array with nested path', () => {
const result = set<number[][]>([], '0.0', 1);
expect(result).toEqual([[1]]);
expect(result[0][0]).toEqual(1);
});
it('should set a value on an array with nested path', () => {
const result = set<number[][][]>([], '0.0.0', 1);
expect(result).toEqual([[[1]]]);
expect(result[0][0][0]).toEqual(1);
});
it('should set a value on an array with nested path', () => {
const arr = [1, 2, 3];
set(arr, 1, 4);
expect(arr).toEqual([1, 4, 3]);
expect(arr[1]).toEqual(4);
});
//--------------------------------------------------------------------------------
// object and array
//--------------------------------------------------------------------------------
it('should set a value on an object and array', () => {
const result = set<Array<{ a: number }>>([], '0.a', 1);
expect(result).toEqual([{ a: 1 }]);
expect(result[0].a).toEqual(1);
});
it('should set a value on an object and array', () => {
const result = set<{ a: number[] }>({}, 'a.0', 1);
expect(result).toEqual({ a: [1] });
expect(result.a[0]).toEqual(1);
});
it('should set a value on an object and array', () => {
const result = set<{ a: number[][] }>({}, 'a.0.0', 1);
expect(result).toEqual({ a: [[1]] });
expect(result.a[0][0]).toEqual(1);
});
it('should set a value on an object and array', () => {
const result = set<{ a: number[][][] }>({}, 'a[0][0][0]', 1);
expect(result).toEqual({ a: [[[1]]] });
expect(result.a[0][0][0]).toEqual(1);
});
//--------------------------------------------------------------------------------
// not support map and set
//--------------------------------------------------------------------------------
it('not support map and set', () => {
expect(() => set<Map<string, number>>(new Map(), 'a', 1)).toThrow(TypeError);
expect(() => set<Set<number>>(new Set(), 1, 2)).toThrow(TypeError);
});
});

57
src/object/set.ts Normal file
View File

@ -0,0 +1,57 @@
/**
* Sets the value at the specified path of the given object. If any part of the path does not exist, it will be created.
*
* @template T - The type of the object.
* @param {Settable} obj - The object to modify.
* @param {Path} path - The path of the property to set.
* @param {any} value - The value to set.
* @returns {T} - The modified object.
*
* @example
* // Set a value in a nested object
* const obj = { a: { b: { c: 3 } } };
* set(obj, 'a.b.c', 4);
* console.log(obj.a.b.c); // 4
*
* @example
* // Set a value in an array
* const arr = [1, 2, 3];
* set(arr, 1, 4);
* console.log(arr[1]); // 4
*
* @example
* // Create non-existent path and set value
* const obj = {};
* set(obj, 'a.b.c', 4);
* console.log(obj); // { a: { b: { c: 4 } } }
*/
export function set<T>(obj: Settable, path: Path, value: any): T {
if (obj instanceof Map || obj instanceof Set) {
throw new TypeError('Set or Map is not supported');
}
//TODO: memoize
const keys = Array.isArray(path)
? path
: String(path as string)
.replace(/\[|\]/g, match => {
return match === '[' ? '.' : '';
})
.split('.');
let pointer: any = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
const nextKey = keys[i + 1];
if (pointer[key] == null || typeof pointer[key] !== 'object') {
pointer[key] = /^\d+$/.test(nextKey as string) ? [] : {};
}
pointer = pointer[key];
}
pointer[keys[keys.length - 1]] = value;
return obj as T;
}
type Settable = object | any[];
type Path = string | number | Array<string | number>;