Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: invert 명확한 타입 추론을 위한 타입 개선 #219

Merged
merged 2 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions docs/docs/utils/object/invert.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@

## Interface
```ts title="typescript"
const invert: (
obj: Record<PropertyKey, any>,
keyTransformer?: (value: any) => PropertyKey
) => Record<PropertyKey, any>;
const invert: <
K extends PropertyKey,
V,
TK extends PropertyKey = V extends PropertyKey ? V : PropertyKey
>(
obj: Record<K, V>,
keyTransformer?: (value: V) => TK
) => Record<K, V>;
```

## Usage
Expand All @@ -24,7 +28,9 @@ import { invert } from '@modern-kit/utils';

const obj = { a: 1, b: 2, c: 3 };

invert(obj); // { 1: 'a', 2: 'b', 3: 'c' };
invert(obj);
// value: { 1: 'a', 2: 'b', 3: 'c' };
// type: Record<number, "a" | "b" | "c">
```

### KeyTransformer
Expand All @@ -35,5 +41,21 @@ const obj = { a: [1, 2, 3], b: [4, 5, 6] };

invert(obj, (value) => {
return JSON.stringify(value);
}); // { '[1,2,3]': 'a', '[4,5,6]': 'b' }
});
// value: { '[1,2,3]': 'a', '[4,5,6]': 'b' }
// type: Record<string, "a" | "b">
```

### const assertion
```ts title="typescript"
import { invert } from '@modern-kit/utils';

const obj = {
a: { name: 'foo' },
b: { name: 'bar' },
} as const;

invert(obj, (value) => value.name);
// value: { foo: 'a', bar: 'b' }
// type: Record<"foo" | "bar", "a" | "b">
```
30 changes: 21 additions & 9 deletions packages/utils/src/object/invert/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import { identity } from '../../common';
import { hasProperty } from '../../validator';

export const invert = (
obj: Record<PropertyKey, any>,
keyTransformer: (value: any) => PropertyKey = identity
const defaultKeyTransformer = <V, TK extends PropertyKey>(value: V) => {
return value as unknown as TK;
};

export const invert = <
K extends PropertyKey,
V,
TK extends PropertyKey = V extends PropertyKey ? V : PropertyKey
>(
obj: Record<K, V>,
keyTransformer: (value: V) => TK = defaultKeyTransformer<V, TK>
Comment on lines +5 to +11
Copy link
Contributor Author

@ssi02014 ssi02014 Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TK는 keyTransformer의 반환 타입입니다.

만약 V가 PropertyKey의 서브타입이면 TK는 V를 기본 타입으로 갖습니다.
반면에 V가 PropertyKey에 호환되지 않으면 TK는 PropertyKey를 기본 반환타입으로 갖습니다.

이를 통해 keyTransformer를 활용해 key가 변환 되더라도 더욱 명확한 타입 추론이 가능하며, keyTransformer가 참조형을 반환하지 않게 방지 할 수 있습니다.

  • 객체의 키는 PropertKey타입에 호환되는게 자연스럽기 때문에 해당 작업을 통해 훨씬 더 의도에 맞게 타입추론을 할 수 있습니다.
const obj = {
  a: { name: 'foo' },
  b: { name: 'bar' },
} as const;
const result = invert(obj, (value) => value);
// '{ readonly name: "foo"; } | { readonly name: "bar"; }' 형식은 'PropertyKey' 형식에 할당할 수 없습니다.
const obj = {
  a: { name: 'foo' },
  b: { name: 'bar' },
} as const;
const result = invert(obj, (value) => value.name);
// { foo: 'a', bar: 'b' }

) => {
const invertedObj: Record<PropertyKey, any> = {};
const invertedObj = {} as Record<TK, K>;

for (const key in obj) {
if (hasProperty(obj, key)) {
const value = obj[key];
const transformedKey = keyTransformer(value);

Object.keys(obj).forEach((key) => {
const value = obj[key];
invertedObj[keyTransformer(value)] = key;
});
invertedObj[transformedKey] = key;
}
}

return invertedObj;
};
27 changes: 25 additions & 2 deletions packages/utils/src/object/invert/invert.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,37 @@ describe('invert', () => {
const result = invert(obj);

expect(result).toEqual({ 1: 'a', 2: 'b', 3: 'c' });
expectTypeOf(result).toEqualTypeOf<Record<number, 'a' | 'b' | 'c'>>();
});

it('should use the keyTransformer if provided', () => {
it('should use the keyTransformer if provided(1)', () => {
const obj = { a: 1, b: 2, c: 3 };
const keyTransformer = (value: number) => `key_${value}`;
const keyTransformer = (value: number): `key_${number}` => `key_${value}`;
const result = invert(obj, keyTransformer);

expect(result).toEqual({ key_1: 'a', key_2: 'b', key_3: 'c' });
expectTypeOf(result).toEqualTypeOf<
Record<`key_${number}`, 'a' | 'b' | 'c'>
>();
});

it('should use the keyTransformer if provided(2)', () => {
const obj = { a: [1, 2, 3], b: [1, 2, 3], c: [1, 2, 3] } as const;
const result = invert(obj, (value) => value[0]);

expect(result).toEqual({ 1: 'c' });
expectTypeOf(result).toEqualTypeOf<Record<1, 'a' | 'b' | 'c'>>();
});

it('should use the keyTransformer if provided(3)', () => {
const obj = {
a: { name: 'foo' },
b: { name: 'bar' },
} as const;
const result = invert(obj, (value) => value.name);

expect(result).toEqual({ foo: 'a', bar: 'b' });
expectTypeOf(result).toEqualTypeOf<Record<'foo' | 'bar', 'a' | 'b'>>();
});

it('should handle an empty object', () => {
Expand Down