Issue
When I swap object key value return type of function is { [x: string]: value } instedof const key.
Playground here
interface IObj {
[key: string]: string;
};
const swapFn = <T extends IObj>(obj: T) => {
const res = {} as { [K in T[keyof T]]: keyof T };
Object.entries(obj).forEach(([key, value]) => {
res[value as T[keyof T]] = key;
});
return res;
}
// return type should be:
// {
// aaa: 'a',
// bbb: 'b',
// } insted of {
// [x: string]: "a" | "b";
// }
const result = swapFn({
a: 'aaa',
b: 'bbb',
});
Solution
There are two problems here. The first is that your return type is too wide, since keyof T
is the union of all the keys; each property will be "a" | "b"
and not the particular "a"
or "b"
corresponding to the value. This can be fixed by changing your mapped type, and can be done particularly easily via key remapping as introduced in TypeScript 4.1:
{ [K in keyof T as T[K]]: K }
We walk through each key K
of T
, and output T[K]
(the property value type) as the key, and K
as the property. This swaps things.
The second problem is one of inference. When you pass in { a: 'aaa', b: 'bbb'}
the compiler is very likely to infer that value as having type {a: string, b: string}
because it is more common that people might want to change the value of a property to some other string
instead of leaving it as some string literal. There are different ways to convince the compiler to infer narrower types. The easiest is for the caller to use a const
assertion:
swapFn({ s: "t", u: "v" } as const);
But if you don't want callers to have to do that, you can use other tricks. See microsoft/TypeScript#30680 for a feature request asking for some less crazy tricks and some description of them. I will add another generic type parameter S extends string
that has no purpose other than to hint to the compiler that you want a string literal type. Instead of T extends IObj
, I will write T extends Record<string, S>
.
Here's the full function:
const swapFn = <T extends Record<string, S>, S extends string>(obj: T) => {
const res = {} as any; // I'm not worried about impl safety
Object.entries(obj).forEach(([key, value]) => {
res[value] = key;
});
return res as { [K in keyof T as T[K]]: K };
}
I changed around the type assertions; the compiler isn't really equipped to verify type safety of a "swap-keys-and-values" implementation so I'm not bothering to try, and using any
.
Let's see if it works:
const result = swapFn({
a: 'aaa',
b: 'bbb',
});
/* const result: {
aaa: "a";
bbb: "b";
} */
console.log(result.aaa.toUpperCase()) // A
Looks good!
Answered By - jcalz
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.