Reputation: 4023
Say I have this interface and an object that contains that type:
interface IMyData {
a: TypeA;
b: TypeB;
c: boolean;
d: string;
}
const myObj: { data: IMyData } = {
data: {
a: someValueA,
b: someValueB,
c: true,
d: "someValueD"
}
}
Now I would like to get a single property from that object and have the function infer the return type:
function getField(obj: { data: IMyData }, field: keyof IMyData){
return obj.data[field];
}
const myFieldStr = getField(myObj, "d"); // should infer type string
const myFieldBool = getField(myObj, "c"); // should infer type boolean
How can I define the getField
function so it infers the return types? Right now it would infer TypeA | TypeB | boolean | string
.
Here is another (more complex?) scenario:
interface IMyValue<T> {
value?: T;
}
interface IMyData2 {
a?: IMyValue<string>;
b?: IMyValue<number>;
c?: IMyValue<boolean>;
d?: IMyValue<string>;
}
function getValue<T extends keyof IMyData2>(field: T, data: IMyData2) {
return data[field] ? data[field]!.value : undefined; // this wouldn't compile without '!' mark
}
const testData: IMyData2 = {
a: { value: 'a' },
b: { value: 2 },
c: { value: false },
};
const testValue1 = getValue('a', testData); // i want this to detect as type of `string`
const testValue2 = getValue('b', testData); // i want this to detect as type of `number`
const testValue3 = getValue('b', testData); // i want this to detect as type of `boolean`
const testValue4 = getValue('b', testData); // i want this to detect as type of `undefined`
Upvotes: 2
Views: 9354
Reputation: 808
A possible solution to the second (more complex) scenario would be to define a new type which will extract the type from the generic:
type GenericOf<S> = S extends IMyValue<infer T> ? T : never;
Now you "plug-in" that type your function:
function getValue<T extends keyof IMyData2>(field: T, data: IMyData2) {
// old solution: return data[field] ? data[field]!.value : undefined;
return data[field]?.value as GenericOf<IMyData2[T]>;
}
This will result the return values of the function to be correctly typed:
const testValue1 = getValue('a', testData); // string | undefined
const testValue2 = getValue('b', testData); // number | undefined
const testValue3 = getValue('c', testData); // boolean | undefined
const testValue4 = getValue('d', testData); // string | undefined
This is not the most elegant solution but it works!
Upvotes: 0
Reputation: 29896
You have to tell the typesystem the exact literal value of field
. The easiest way to do that is to use a generic function like this:
interface IMyData {
c: boolean;
d: string;
}
const myObj: { data: IMyData } = {
data: {
c: true,
d: "someValueD"
}
}
function getField<T extends keyof IMyData>(obj: { data: IMyData }, field: T){
return obj.data[field];
}
const myFieldStr = getField(myObj, "c"); // should infer type boolean
const myFieldBool = getField(myObj, "d"); // should infer type string
Or in the simplest general case:
function pluck<T, K extends keyof T>(obj : T, key : K) {
return obj[key];
}
const foo = pluck({ bar: "asd", baz: 5 }, "bar"); // infers string
Upvotes: 7