Reputation: 4715
I want to type a function called pick
, I know typescript has built-in Pick<T, K>
, now I want to implement the actual source code usage part, but I'm stuck.
What this function do is to pick
provided object
's property based on given strings and return new object
.
The usage is this:
const data = { name: 'Jason', age: 18, isMember: true };
const pickedData = pick(data, ['name', 'isMember']);
console.log(pickedData)
// => { name: 'Jason', isMember: true }
My pick
function's javascript implementation without typescript
is this:
function pick(obj, properties) {
let result = {}
properties.forEach(property => {
result[property] = obj[property];
});
return result;
}
Here is the typescript
version, but it's not compiling:
function pick<T, K extends keyof T>(obj: T, properties: K[]): Pick<T, K> {
let result: Pick<T, K> = {}; // Typescript yells: Type '{}' is not assignable to type 'Pick<T, K>'.
properties.forEach(property => {
result[property] = obj[property];
});
return result;
}
My VSCode screenshot:
But, there is one solution, I don't know if this is a good
solution, that is to add as Pick<T, K>
:
function pick<T, K extends keyof T>(obj: T, properties: K[]): Pick<T, K> {
let result: Pick<T, K> = {} as Pick<T, K> ; // Typescript now satisfy with this.
properties.forEach(property => {
result[property] = obj[property];
});
return result;
}
However, when using this pick
function, typescript
doesn't show the picked object property, instead, it shows unnecessary information:
It shows me this:
// typescript shows me, not what I want.
const pickedData: Pick<{
name: string;
age: number;
isMember: boolean;
}, "name" | "isMember">
Is there any way that typescript
can show me this?
// This is what I want typescript to show me.
const pickedData: {
name: string;
isMember: boolean;
}
Upvotes: 2
Views: 3391
Reputation: 249726
The first problem is that {}
should not be assignable to Pick<T, K>
since Pick<T, K>
will probably have required properties. There are some instances where a type assertion is the only way to get things to work and this is one of those. Typescript does not know that you plan to assign those properties a bit later, you have that information and provide it to the compiler in the form of the type assertion, basically saying, relax, I know this isn't really Pick<T, K>
but I will make it into that shortly.
The second part of you issue is not so much a functional one Pick<{ name: string; age: number; isMember: boolean;, "name" | "isMember">
is structurally equivalent to { name: string; isMember: boolean; }
. Sometimes the language service expands these types, sometimes it does not. This is a recent issue describing this exact behavior.
You can force an expansion of the type with a more complicated type (an identity mapped type in an intersection with {}
):
type Id<T extends object> = {} & { [P in keyof T]: T[P] }
function pick<T, K extends keyof T>(obj: T, properties: K[]): Id<Pick<T, K>> {
let result: Pick<T, K> = {} as Pick<T, K> ; // Typescript now satisfy with this.
properties.forEach(property => {
result[property] = obj[property];
});
return result;
}
const data = { name: 'Jason', age: 18, isMember: true };
const pickedData = pick(data, ['name', 'isMember']);
console.log(pickedData)
Just as a warning, there are no guarantees Id
will always be expanded, all I can say is that in the current version Id
seems to always force the expansion of the mapped type.
Upvotes: 5